Pi compaction stalls after tool result; manual /compact times out after 300s without cancelling underlying work
Summary
On Craft Agents 0.13.3 (macOS, Apple Silicon), a long conversation stopped progressing after a successful tool result around the automatic compaction threshold. The UI showed compaction and then remained in Thinking. Subsequent messages did not produce a response. A later manual /compact failed with:
compact timed out after 300sInspection of the installed bundled code and isolated tests identified two recovery weaknesses: the host's manual-compaction timeout does not send cancellation, and the subprocess can spend the entire host deadline waiting for an existing compaction before attempting manual compaction.
These findings do not establish why the original summarization request stalled. Please investigate both the initial stall and timeout/cancellation recovery.
Environment
- Craft Agents: 0.13.3 (installed package.json)
- macOS / arm64
- Affected trace: Pi backend, ChatGPT/Codex connection (
openai-codex-responses), highest user-selected thinking level (max) - Configured model context window: 272,000 tokens
- The user has also observed slow/stalled compaction with other models, but those cases have not been independently traced. This report's code findings are specific to the inspected Pi path.
Observed sequence
Times are local UTC+8, September 18, 2026:
- 16:07:55: assistant emitted a tool call. Recorded usage.totalTokens: 256,774.
- 16:07:56: tool successfully returned in under a second.
- The user saw compaction followed by persistent Thinking. No subsequent assistant message or new compaction entry was persisted in the underlying Pi JSONL during inspection.
- Follow-up user messages appeared in the UI session JSONL but had not appeared in the underlying Pi conversation JSONL.
- 16:27:53:
/compactwas saved in the UI session record. - The user then received
compact timed out after 300s.
Absence of a new compaction entry proves no completed compaction was persisted at inspection time; it does not prove that no request was started. A manual /compact need not itself appear as a regular Pi user message.
Code findings in the installed bundles
1. Host timeout does not cancel the subprocess operation
In dist/main.cjs, requestCompact(customInstructions) sends { type: "compact", id, customInstructions } and starts a 300,000 ms timer. Its timeout callback only does:
this.pendingCompactions.delete(id);
reject(new Error(`compact timed out after ${Math.floor(timeoutMs / 1e3)}s`));There is no cancellation sent in this callback. Consequently, host timeout alone does not establish that the underlying work has stopped.
2. The child can exhaust the host deadline just waiting for old compaction
In resources/pi-agent-server/index.js:
async function handleCompact(msg) {
// ...
const session = await ensureSession();
await waitForCompaction(session);
const result = await session.compact(msg.customInstructions);
// send compact_result ...
}waitForCompaction(session, timeoutMs = 300000) polls session.isCompacting. On timeout it logs proceeding anyway and breaks; it does not cancel the old compaction or throw.
Thus, if the previous compaction remains active, the child waits approximately the entire outer RPC deadline before even entering session.compact().
Important nuance: session.compact() itself begins with await this.abort(), and abort() calls abortCompaction() and waits for idle. This report does not claim there is no abort mechanism anywhere; the problem is delayed entry and lack of cancellation in the host timeout path. Whether the original operation responds to abort needs live diagnosis.
3. Summary work inherits conversation reasoning, with no lifecycle deadline at the inspected layer
_runDefaultCompaction() passes this.thinkingLevel to compact(). createSummarizationOptions() forwards that level for reasoning models. This means expensive reasoning settings also affect summarization.
_runAutoCompaction() awaits summarization with an AbortController but no overall deadline at this inspected lifecycle layer. Lower-level transport protection may exist; I have not established its effectiveness for this incident.
4. The threshold matches the observed tool boundary
Defaults found in the bundle:
reserveTokens: 16384,
keepRecentTokens: 20000The threshold is contextWindow - reserveTokens, i.e. 255,616 for the configured 272,000 window. The recorded usage of 256,774 exceeds that threshold. _compactBeforeNextAssistantResponse() awaits automatic compaction before the next model response, consistent with the observed pause after the tool result.
Isolated verification (no model/network requests)
I extracted the installed requestCompact, waitForCompaction, and handleCompact functions into Node VM tests with mocked session methods and virtual timers:
- Triggered the host deadline: promise rejected with the exact 300s error; pending record was removed; the only outgoing message remained
compact(no cancellation). - Kept a mock
session.isCompacting = true: child advanced through 300,200 ms of virtual polling, loggedproceeding anyway, and entered the mockedsession.compact()while the prior flag remained true.
These tests verify control flow, not the original provider/network stall, live cancellation behavior, or loss of data.
Expected behavior / suggested investigation
- Use one bounded lifecycle/deadline for waiting, summary generation, and completion; avoid nested full-length deadlines.
- On timeout, cancel the matching operation and acknowledge cleanup, or isolate the unhealthy session worker before allowing new work.
- Ignore stale completion events/results using operation identity so expired work cannot affect a newer operation.
- Preserve the pre-compaction history on failure; only commit a complete valid summary.
- Consider a separate supported lower reasoning setting for summaries, without changing normal task reasoning.
- Show waiting-for-existing-compaction vs generating-summary vs retrying/cancelling, instead of indefinite Thinking.
- Test tool-boundary auto-compaction, manual compact during existing compaction, nonresponsive requests, abort, late completion, and recovery of the next user message.
Diagnostics / limitations
No application code or conversation history was modified during investigation. The original session contains private business material and is intentionally not attached. No credentials or raw model payloads are included.
A read-only code-signature check reported two additional WASM resources in the installed app (resources/pi-agent-server/photon_rs_bg.wasm and vendor/bun/photon_rs_bg.wasm). Their origin is unknown; no causal connection to this issue has been established. This is disclosed in case installed-package integrity matters to reproduction.
Please advise how to safely collect compaction lifecycle/transport diagnostics without exposing conversation content.
Source: craft-ai-agents/craft-agents-oss