a command dropped for want of a sidecar has to say so

`sendFire` looked up a sidecar, and if none was registered it did nothing.
No log, no error, no return value — the command simply stopped existing. That
window is real and entirely predictable: every `pm2 restart officer` has one
while the sidecars reconnect, and somebody clicking a button has no idea they
are in it.

The cost landed hardest on answering a question. `answerClaudeQuestion` in the
agent documents this exact failure in a careful comment and cannot report it,
because the command never arrives to be reported. So the question card kept
spinning, which from the person's side is indistinguishable from a slow agent,
and the only move left is to send another message — the worst possible thing to
do to a waiting tool call. Observed tonight, on a restart I ran myself while a
commander was mid-question.

So `sendFire` returns whether anyone took the command and warns when nobody did,
and the two paths where a person is actively waiting — a question answer and a
plan decision — tell them instead of leaving the card to spin.

The other five callers are unchanged and now leave a log line, which is the part
that was missing when this was diagnosed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 00:06:30 +00:00
co-authored by Claude Opus 5
parent 383df51a99
commit 5c802b07b5
2 changed files with 50 additions and 9 deletions
+26 -2
View File
@@ -1396,13 +1396,20 @@ function handlePlanDecision(
): void {
const sessionId = wsToSessionMap.get(ws);
if (!sessionId) return;
sidecar.decideClaudePlan({
const delivered = sidecar.decideClaudePlan({
sessionKey: sessionId,
userId: ws.data.userId,
requestId: msg.requestId,
approve: msg.approve,
feedback: msg.feedback,
});
// Same drop as `handleQuestionAnswer`, same spinning card — see `sendFire`.
if (!delivered) {
sendToClient(ws, {
type: 'error',
message: 'The agent is not connected right now, so that decision did not reach it. Try again in a moment.',
});
}
}
/**
@@ -1416,12 +1423,29 @@ function handlePlanDecision(
function handleQuestionAnswer(ws: ServerWebSocket<WSData>, msg: { toolCallId: string; answer: string }): void {
const sessionId = wsToSessionMap.get(ws);
if (!sessionId) return;
sidecar.answerClaudeQuestion({
/*
A dropped answer has to reach the person who sent it.
`sendFire` does nothing when no sidecar is registered, and that window is real — every restart of this
process has one while the agent reconnects. The answer vanished with no log and no error, so the
question card spun on, which is indistinguishable from a slow agent. The only move left was to send
another message, which is the worst possible thing to do to a waiting tool call.
The agent's own handler already reports the failures it CAN see (no such session, question no longer
waiting). This is the one it cannot, because the command never arrives.
*/
const delivered = sidecar.answerClaudeQuestion({
sessionKey: sessionId,
userId: ws.data.userId,
toolCallId: msg.toolCallId,
answer: msg.answer,
});
if (!delivered) {
sendToClient(ws, {
type: 'error',
message: 'The agent is not connected right now, so that answer did not reach it. Try again in a moment.',
});
}
}
async function handleStop(ws: ServerWebSocket<WSData>): Promise<void> {