Skip to content

Structured annotate gates wait forever after every review client disconnects #1142

Description

@rNoz

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:

  1. plannotator annotate ./document.md --gate --json
  2. Wait for the review UI to load.
  3. Close every tab or window connected to the review, without approving, sending feedback, or using Close.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions