What happened?
A local direct plannotator annotate <file> --gate --json waits for one decision: approved, annotated, or dismissed. Close every browser tab connected to that review without choosing one, and the CLI keeps waiting with no timeout and no way to recover except killing it.
Steps:
plannotator annotate ./document.md --gate --json
- Wait for the review UI to load.
- Close every tab or window connected to the review, without approving, sending feedback, or using Close.
- The CLI is still waiting. It stays that way indefinitely.
The same state happens when the renderer or browser process disappears without delivering /api/exit.
Expected: after at least one client has connected, losing the last one should start a documented reconnect grace period; a reconnect inside it continues the same review, and expiry resolves the gate as dismissed. Losing one of two tabs must not dismiss anything while the other is still connected.
Worth stating because it constrains the fix: pagehide and beforeunload are not close signals. They also fire on reload and navigation, so dismissing from them would kill reviews the user is about to come back to. The observable facts are connection-level: whether a client ever connected, whether one is still connected, and whether one reconnected. So is one more requirement: whichever outcome settles the session first has to be the only one, and a decision arriving after that must be refused rather than acknowledged, because a tab reporting success for an outcome the caller never received is worse than a visible error.
Out of scope for this report: a session whose browser never opens at all (callers still need their own startup timeout), durable feedback recovery after a caller timeout (#678), headless session ownership (#982), and hook or legacy plaintext annotate output.
Plannotator version
0.25.0 (reproduced from source at main a54b46bb).
OS
macOS
Agent
Claude Code
Where did it happen?
Annotate
Related issues
Those overlap lifecycle or recovery, but none of them defines last-client abandonment for direct structured gates.
The strict-gate exit contract from #1091 is unaffected: an abandonment dismissal is an ordinary dismissed decision, so it publishes normally and exits 1 only under --require-approval. Exit 2 stays reserved for a misconfigured gate, a strict startup failure, or a failed result-file publication.
What happened?
A local direct
plannotator annotate <file> --gate --jsonwaits for one decision: approved, annotated, or dismissed. Close every browser tab connected to that review without choosing one, and the CLI keeps waiting with no timeout and no way to recover except killing it.Steps:
plannotator annotate ./document.md --gate --jsonThe same state happens when the renderer or browser process disappears without delivering
/api/exit.Expected: after at least one client has connected, losing the last one should start a documented reconnect grace period; a reconnect inside it continues the same review, and expiry resolves the gate as
dismissed. Losing one of two tabs must not dismiss anything while the other is still connected.Worth stating because it constrains the fix:
pagehideandbeforeunloadare not close signals. They also fire on reload and navigation, so dismissing from them would kill reviews the user is about to come back to. The observable facts are connection-level: whether a client ever connected, whether one is still connected, and whether one reconnected. So is one more requirement: whichever outcome settles the session first has to be the only one, and a decision arriving after that must be refused rather than acknowledged, because a tab reporting success for an outcome the caller never received is worse than a visible error.Out of scope for this report: a session whose browser never opens at all (callers still need their own startup timeout), durable feedback recovery after a caller timeout (#678), headless session ownership (#982), and hook or legacy plaintext annotate output.
Plannotator version
0.25.0(reproduced from source atmaina54b46bb).OS
macOS
Agent
Claude Code
Where did it happen?
Annotate
Related issues
Those overlap lifecycle or recovery, but none of them defines last-client abandonment for direct structured gates.
The strict-gate exit contract from #1091 is unaffected: an abandonment dismissal is an ordinary
dismisseddecision, so it publishes normally and exits1only under--require-approval. Exit2stays reserved for a misconfigured gate, a strict startup failure, or a failed result-file publication.