feat(server): always surface the session URL on every start - #1184
feat(server): always surface the session URL on every start#1184technicalpickles wants to merge 2 commits into
Conversation
handleServerReady printed the session URL on three paths only: a remote session, the Codex desktop host, or a browser that failed to open. The common case, a local session whose browser opened fine, printed nothing, so a closed tab or a second machine left neither the user nor the agent driving the session any way back to it. Two changes, in the order the issue lays out: 1. One unconditional stderr line on every start, "Plannotator session ready: <url>". One line, URL last, stable format, because agents grep it. The remote and failed-launch branches now add context lines and never repeat the URL. The Codex desktop case existed solely to print the URL when the browser opened, so it and isCodexDesktopHost go with it. 2. PLANNOTATOR_READY_FILE now defaults to <dataDir>/ready.jsonl. The side channel already worked, but only amp and the OpenCode bridge set it; Claude Code spawns the CLI straight from a hook, so nothing set the variable and the metadata the server already publishes landed nowhere. apps/hook calls ensureReadyFileEnv() at startup, which leaves a host-supplied path alone, so tail -n 1 ready.jsonl is the newest session. The Pi runtime mirrors change 1 through its own surface: it owns the terminal, so openBrowserForServer notifies unconditionally instead of writing to stderr. Pi never publishes a ready file, so change 2 has no counterpart there. Also: the OpenCode stderr forwarder keeps forwarding the remote follow-up line under its new phrasing, ready.jsonl joins the uninstall purge list, and the env-var reference plus the troubleshooting "lost a tab" section document both recovery routes. Criterion 2 of the issue, a stable port across plan resubmissions, is deliberately not here. It is a separate PR. Refs backnotprop#1134
Review (at
|
Review follow-up on backnotprop#1184. Drop the always-on ready file. The default duplicated the session registry that already exists (`packages/server/sessions.ts`, surfaced by `plannotator sessions`) with strictly less information: no pid, no timestamp, no liveness. It also grew unboundedly on pure append, and the documented `tail -n 1` returned the last session *started* rather than the last one *alive*, which is exactly the stale-server confusion this branch set out to kill. The registry already answers the question, so only the default goes. `PLANNOTATOR_READY_FILE` stays as the opt-in host side channel and host-supplied values behave as before, so amp and OpenCode are unaffected. Make the format contract real. The assertions interpolated `SESSION_READY_LINE_PREFIX` into the expected string, so they compared the constant against itself: retexting it left every one of those tests green while `cli-bridge.ts` went on matching a hardcoded `/^Plannotator session ready\b/`, which means OpenCode users would have silently lost the URL with zero test failures. The two leading spaces in the emitted line were never asserted either. Now the literal bytes are pinned once, the emitted line is asserted including its indent and surrounding newlines, and a cross-component test in `cli-bridge.test.ts` feeds a line built from the exported constant through `formatUserFacingCliStderrLine`. Retexting the prefix fails 5 tests. Forward the browser-launch failure in OpenCode. "Could not open a browser automatically" matched none of the filter regexes, so the new output shape dropped the one line that explains a tab which never appeared. The old shape got forwarded; this was a regression introduced here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This is a better review than I gave you a PR for. Both items are fixed, and you were right on the design call. 1, the format contract. Yeah, that one stings. Asserting against Pinned three ways now: the literal bytes, the emitted line asserted exactly with its two-space indent and newlines, and a cross-component test in Then I mutated the constant to see what happened. Five tests go red, the cross-component one included. Reverted. 2, the ready-file default is gone. You talked me out of it. So Now the part I didn't fix, before you find it yourself on re-review: the contract is pinned for the Bun runtime and OpenCode, and not for Pi. The dropped browser-failure line was ours, and it's fixed. "Could not open a browser automatically" matched none of the filter regexes, so OpenCode users got a URL and nothing about why no tab showed up, where the old shape forwarded it. Matched and covered. Body's fixed too. It advertised Marking it ready for review. |
Delta re-review (at
|
Part of #1134, criterion 1 only. Leaving the stable-port half for a separate PR as you suggested, since the abandoned-session cleanup looked like its own conversation.
Summary
ctx.ui.notify, and the OpenCode stderr filter learned the new phrasing while still forwarding the older two-line shape from older binaries.isCodexDesktopHost()is gone. It existed only to special-case printing the URL, which is now unconditional.PLANNOTATOR_READY_FILEstays exactly what it was, an opt-in side channel that hosts point at their own temp file. No default, per your review. Discovery for everyone else is the stderr line plus the existingplannotator sessionsregistry, which is what the docs now say.The format is deliberately boring and stable, since the point is that agents grep it:
Two leading spaces, no trailing slash. Remote adds a port-forwarding line after it, and a failed browser launch adds a "could not open" line. The URL itself appears exactly once either way.
Test plan
bun run typecheckis clean.bun testhas four failures that predate this branch, and none of them are in code it touches. Three are inapps/pi-extension/server.test.ts, where a globalgpg.format=sshconfig leaks into the test's temp HOME and git can't write a commit object; that file imports only./server.tsplus the generated modules. The fourth is the git-timeout case inpackages/shared/workspace-status.test.ts, which lines up with the parallel-timing flakes you mentioned: this branch doesn't touchpackages/shared/at all, and that file passes 6/6 in isolation across three consecutive runs.New coverage in
packages/server/shared-handlers.test.tspins the URL appearing exactly once on the local, remote, and browser-failed paths, and pins the line's literal bytes including the two-space indent.apps/opencode-plugin/cli-bridge.test.tsimportsSESSION_READY_LINE_PREFIXand runs a line built from it through the real filter, so drifting the constant now breaks a test instead of silently costing OpenCode users the URL.