Skip to content

app: bound host joins and release drains - #2014

Merged
AbirAbbas merged 1 commit into
app/v3from
fix/app-host-join-budget
Oct 28, 2026
Merged

AbirAbbas merged 1 commit into
app/v3from
fix/app-host-join-budget

Conversation

@AbirAbbas

Copy link
Copy Markdown
Collaborator

What was wrong

When the desktop opens a chat that a terminal codeaf is hosting, app mode joins that terminal's session host. appJoinExisting called remote.Dial, which writes hello and then waits for the welcome with no deadline, while holding the chat's gate. If the terminal host is busy, wedged or stopped, the desktop's open stays pending until the host answers again. Users who run the TUI and the desktop side by side read this as "the engine is waiting".

Separately, appBackend.finish shared one time.After channel across several waits. After the first wait consumed its value, a later unfinished producer could block the release forever.

What changes (app mode only)

  • Host join budget. One 5 s budget covers starting-host discovery, the held-transcript host search and every welcome. Each connect is capped by the time left, and the socket gets a deadline that is cleared on success, so long turns after the handshake are unaffected. Failed sockets are closed. When the budget runs out, the open returns unreachable: "This chat didn't answer from the other codeaf window. Try again, or continue there." The desktop already shows that in its existing "Couldn't open chat / Try again" receipt. No takeover, no reopen of a locked journal, no replayed send. The terminal's own remote.Dial is unchanged.
  • Release drain. One closing deadline channel is shared by all waits, so the drain always reaches box.Close(). A clean close still drains everything it produced.

Validation contract → tests

  1. Silent host: the first open returns unreachable within the budget, releases the gate and closes the socket; other requests keep answering (TestAppHostJoinSilentOpenReleasesGate).
  2. Several silent candidates share one budget (…CandidatesShareBudget, …StartingHostSharesBudget).
  3. A healthy host joins as before; a turn longer than the budget continues (…RetryAndLongTurn).
  4. Retry after the host answers joins the same chat. No new entries, tasks or provider calls, and no fd/goroutine growth per failed attempt (…FailedCandidatesClose).
  5. finish with stuck producers returns within the release wait; clean producers drain promptly (TestAppFinishSharesOneDeadline, TestAppFinishDrainsCleanProducersPromptly).

Manual verification on a real engine

Linux, isolated temporary homes, a local OpenAI-compatible stub, and a real codeaf engine --daemon host holding the chat. Only the host was SIGSTOPped for 12 s while the app called agents.open:

Binary Open returned after (3 runs) Result
app/v3 before 12.02 / 12.02 / 12.02 s, only after the host resumed the chat
this branch 5.00 / 5.00 / 5.00 s, host still paused unreachable (copy above)

On both binaries, agents.list and models.list kept answering in milliseconds during the pause. In all six runs, the retry right after resume joined the original chat. Transcript bytes were unchanged, with no new entries, tasks or provider calls. A healthy join followed by a 16 s turn completed on both binaries.

Checks

gofmt, go vet ./cmd/codeaf, and the named tests above: 18 cases, all passing. CI (ci.yml, ci-full.yml) was dispatched on this branch; it is judged against an app/v3 Full baseline because app/v3's own Full is red.

Merge order: after #2009 lands in app/v3 (rebase then). This branch does not touch #2011's hunks.

🤖 Generated with Claude Code

Share one five-second budget across desktop host discovery and welcomes.
Return a retryable unreachable receipt, close failed sockets and clear the
deadline on successful joins without changing terminal dialing policy.

Drain conversation producers against one closing deadline channel. Cover
silent hosts, retry, long turns and delayed clean producers with fakes.
Verify timeout consumption with the shortened timer hook, and document
the desktop behavior.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant