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:
@@ -224,11 +224,28 @@ function sendCommand(kind: string, cmd: SidecarCommand, timeoutMs = DEFAULT_TIME
|
||||
});
|
||||
}
|
||||
|
||||
function sendFire(kind: string, cmd: SidecarCommand): void {
|
||||
/**
|
||||
* Fire a command at whichever sidecar handles this kind. Returns whether one was there to take it.
|
||||
*
|
||||
* It used to return void and drop the command when no sidecar was registered — no log, no error, nothing.
|
||||
* That window is real and predictable: every `pm2 restart officer` has one while the sidecars reconnect, and
|
||||
* a person clicking a button has no idea they are in it.
|
||||
*
|
||||
* The cost landed on `claude:question-answer`, whose own handler documents this exact failure and cannot
|
||||
* report it, because the message never arrives to be reported. From the person's side the question card just
|
||||
* kept spinning — indistinguishable from a slow agent, so the only move left was to send another message.
|
||||
*
|
||||
* Callers that can tell somebody are now able to. The boolean is the whole mechanism; the warning is for
|
||||
* whoever reads the log afterwards wondering why one click did nothing.
|
||||
*/
|
||||
function sendFire(kind: string, cmd: SidecarCommand): boolean {
|
||||
const sc = findSidecarHandling(kind);
|
||||
if (sc) {
|
||||
sc.ws.send(JSON.stringify(cmd));
|
||||
if (!sc) {
|
||||
console.warn(`[sidecar] dropped ${cmd.type}: no sidecar registered for "${kind}"`);
|
||||
return false;
|
||||
}
|
||||
sc.ws.send(JSON.stringify(cmd));
|
||||
return true;
|
||||
}
|
||||
|
||||
function sendCommandToSidecar(
|
||||
@@ -368,8 +385,8 @@ export function decideClaudePlan(params: {
|
||||
requestId: string;
|
||||
approve: boolean;
|
||||
feedback?: string;
|
||||
}): void {
|
||||
sendFire('claude', { type: 'claude:plan-decision', id: nextId(), ...params });
|
||||
}): boolean {
|
||||
return sendFire('claude', { type: 'claude:plan-decision', id: nextId(), ...params });
|
||||
}
|
||||
|
||||
// The user's answer to an AskUserQuestion. Fire-and-forget for the same reason as the plan decision: the
|
||||
@@ -380,8 +397,8 @@ export function answerClaudeQuestion(params: {
|
||||
userId: number;
|
||||
toolCallId: string;
|
||||
answer: string;
|
||||
}): void {
|
||||
sendFire('claude', { type: 'claude:question-answer', id: nextId(), ...params });
|
||||
}): boolean {
|
||||
return sendFire('claude', { type: 'claude:question-answer', id: nextId(), ...params });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user