When using this plugin within oh-my-pi under the iOA network environment, I frequently encounter this warning:
Warning: CodeBuddy SDK: 1 tool handler(s) still waiting — provider may be stuck.
However, after I retried, the LLM seemed unable to access the previous context to continue the conversation.
I asked AI to analyze this issue and propose a solution; I’m not sure if it will be helpful to you, but I hope it resolves the problem:
Title: Fatal unhandledRejection on retry-after-interrupt: fire-and-forget handleMcpMessageRequest throws on torn-down transport (root cause in @tencent-ai/agent-sdk); same retry also silently drops session history
Summary
Two related defects triggered by one user action — pressing retry after interrupting a wedged turn:
- Fatal crash (root cause in the upstream @tencent-ai/agent-sdk dependency): ProcessTransport.handleLine dispatches control_request/mcp_message via fire-and-forget handleMcpMessageRequest() with no await / .catch. When the CodeBuddy CLI subprocess
has just been torn down (interrupt → retry), a late-arriving mcp_message makes sendControlErrorResponse → writeLine throw Error: Transport not started inside an unowned async function → unhandled rejection → the host process (oh-my-pi) escalates
unhandledRejection to kind: "fatal" and exits.
- Silent context loss (plugin side): on that same retry, when pi's rewritten history is shorter than the stored cursor, sharedSession takes the "Case 1 synthetic: clean start for shorter context" branch and sends only the final user message with no
resume:. The model then truthfully reports it has no memory of the previous conversation.
Environment
- pi-codebuddy-sdk 0.4.4
- @tencent-ai/agent-sdk 0.3.244 (dependency)
- Host: oh-my-pi 18.1.21, plugins loaded under Bun, Windows 11
Crash mechanism (code level)
In @tencent-ai/agent-sdk/lib/transport/process-transport.js (line numbers approximate — from the installed 0.3.244; the crashing build was a slightly older patch):
- ~L768 — handleLine() receives control_request/mcp_message and calls this.handleMcpMessageRequest(controlRequest) fire-and-forget: no await, no .catch. The comment above says this is intentional (control requests must still dispatch after the
consumer's for-await loop is cancelled) — but the returned promise is unowned.
- ~L956 / ~L961 / ~L984 — handleMcpMessageRequest: when the target transport can't be found, it calls sendControlErrorResponse directly, outside any try/catch; the general error path also funnels into sendControlErrorResponse.
- ~L650 → ~L844 — sendControlErrorResponse → writeLine: if (!this.process?.stdin) throw new Error('Transport not started').
Observed sequence in my session:
- The turn wedges: the plugin logs CodeBuddy SDK: 1 tool handler(s) still waiting — provider may be stuck (plugin index.ts ~L1134) — a tool callback is never collected by the CLI.
- I interrupt, then press retry. Retry tears down the CLI subprocess; stdin is destroyed.
- ~15s later a late mcp_message control request arrives on the half-dead pipe → steps 1→2→3 → the throw lands in an unowned async function → unhandled rejection → host's default handler terminates the process as fatal. (Only
ipc-send/stdio-write/expected-cleanup errors are swallowable in the host; this rejection is none of those.)
Context-loss mechanism (plugin side)
index.ts ~L522–597, sharedSession { sessionId, cursor }:
- Normal path REUSE: sends only the delta; the CLI reads its own session file.
- Divergent history → REBUILD: rewrites pi's full history into a new CLI session.
- ~L554–557 — "Case 1 synthetic: clean start for shorter context": when the delivered history is shorter than the cursor (priorMessages.length < cursor), return { sessionId: null } → the query goes out without resume: and contains only the last user
message.
On retry, pi rewrites history (branch summary discards the aborted branch; the log shows advisor-delivered prefix changing from index 92 on), so the delivered history shrinks → clean-start branch fires → the only thing on the wire was a synthetic
[tool_result:call_…] [continue] message. The model first returned an empty stop, then (accurately) stated it had no context of the prior conversation.
Reproduction
- Session with an MCP tool call in flight.
- Provider wedges (tool handler never collected → provider may be stuck warning). I don't have a deterministic trigger for the wedge itself.
- Interrupt, then retry while pi's history rewrite makes the visible history shorter (e.g. right after a branch summary).
- Observe: process exits with fatal unhandledRejection (Transport not started), stack: writeLine ← sendControlErrorResponse ← handleMcpMessageRequest ← handleLine. After relaunch/resume, the model has no context.
Suggested fixes
- SDK (root fix): attach rejection handling to every fire-and-forget dispatch in handleLine, e.g.
void this.handleMcpMessageRequest(controlRequest).catch(() => {});
Swallowing is appropriate here: the transport is mid-teardown and the error response is undeliverable anyway. Alternative: make writeLine/sendControlErrorResponse a no-op (instead of throwing) when stdin is already destroyed.
- Plugin (defense in depth): make the clean-start branch visible — warn the user that history was dropped — or fall back to REBUILD with the truncated history instead of sending a single synthetic message.
Workaround (for users, until fixed)
When CodeBuddy SDK: N tool handler(s) still waiting — provider may be stuck appears, do not retry in place. Exit and resume with omp --resume — the local transcript is intact. The crash only occurs in the window where in-place retry has
torn down the transport while the control pipe is still alive.
When using this plugin within oh-my-pi under the iOA network environment, I frequently encounter this warning:
Warning: CodeBuddy SDK: 1 tool handler(s) still waiting — provider may be stuck.However, after I retried, the LLM seemed unable to access the previous context to continue the conversation.
I asked AI to analyze this issue and propose a solution; I’m not sure if it will be helpful to you, but I hope it resolves the problem:
Title: Fatal unhandledRejection on retry-after-interrupt: fire-and-forget handleMcpMessageRequest throws on torn-down transport (root cause in @tencent-ai/agent-sdk); same retry also silently drops session history
Summary
Two related defects triggered by one user action — pressing retry after interrupting a wedged turn:
has just been torn down (interrupt → retry), a late-arriving mcp_message makes sendControlErrorResponse → writeLine throw Error: Transport not started inside an unowned async function → unhandled rejection → the host process (oh-my-pi) escalates
unhandledRejection to kind: "fatal" and exits.
resume:. The model then truthfully reports it has no memory of the previous conversation.
Environment
Crash mechanism (code level)
In @tencent-ai/agent-sdk/lib/transport/process-transport.js (line numbers approximate — from the installed 0.3.244; the crashing build was a slightly older patch):
consumer's for-await loop is cancelled) — but the returned promise is unowned.
Observed sequence in my session:
ipc-send/stdio-write/expected-cleanup errors are swallowable in the host; this rejection is none of those.)
Context-loss mechanism (plugin side)
index.ts ~L522–597, sharedSession { sessionId, cursor }:
message.
On retry, pi rewrites history (branch summary discards the aborted branch; the log shows advisor-delivered prefix changing from index 92 on), so the delivered history shrinks → clean-start branch fires → the only thing on the wire was a synthetic
[tool_result:call_…] [continue] message. The model first returned an empty stop, then (accurately) stated it had no context of the prior conversation.
Reproduction
Suggested fixes
Swallowing is appropriate here: the transport is mid-teardown and the error response is undeliverable anyway. Alternative: make writeLine/sendControlErrorResponse a no-op (instead of throwing) when stdin is already destroyed.
Workaround (for users, until fixed)
When CodeBuddy SDK: N tool handler(s) still waiting — provider may be stuck appears, do not retry in place. Exit and resume with omp --resume — the local transcript is intact. The crash only occurs in the window where in-place retry has
torn down the transport while the control pipe is still alive.