Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
1397c44
docs(plan): plan accepting Codex 0.157.1
Juliusolsson05 Sep 27, 2026
cd626df
chore(upstream): accept Codex 0.157.1
Juliusolsson05 Sep 27, 2026
8485768
Merge remote-tracking branch 'origin/main' into chore/accept-codex-0157
Juliusolsson05 Sep 27, 2026
b9674e0
docs(plan): record the known gaps found in the Codex 0.157.1 review
Juliusolsson05 Sep 27, 2026
290bb30
fix(opencode): spawn the TUI without waiting on opencode db path (#1114)
Juliusolsson05 Sep 27, 2026
e55d9e2
fix(opencode): bound the pre-paint input hold and report refused inpu…
Juliusolsson05 Sep 27, 2026
4352a7e
fix(opencode): report a refused pre-attach flush; neutral refusal cop…
Juliusolsson05 Sep 27, 2026
4908ee0
test(opencode): pin the hold's restart and stop guards; fix stale del…
Juliusolsson05 Sep 27, 2026
e66781a
fix(user-mcp): roll the file back too when a secret step fails after …
Juliusolsson05 Sep 27, 2026
f0f01bb
fix(user-mcp): a failed secret step restores the server's previous se…
Juliusolsson05 Sep 27, 2026
1cb39fb
fix(user-mcp): a destination never pairs with a token not saved for i…
Juliusolsson05 Sep 27, 2026
40a408b
fix(user-mcp): type the pending secret restore explicitly (tsc narrow…
Juliusolsson05 Sep 27, 2026
0dc2ad1
fix(user-mcp): read the pending secret restore through a method (tsc …
Juliusolsson05 Sep 27, 2026
3287edb
fix(user-mcp): bind every secret to the destination it was saved for;…
Juliusolsson05 Sep 27, 2026
97226ab
fix(user-mcp): never auto-bind a pre-binding secret; keep it, withhol…
Juliusolsson05 Sep 27, 2026
fb213d7
fix(user-mcp): count the literal around a secret reference in the des…
Juliusolsson05 Sep 27, 2026
ac2d797
test(user-mcp): an unlistable secrets dir fails the save and the toke…
Juliusolsson05 Sep 27, 2026
4d5c79a
fix(user-mcp): bind secrets to the raw entry, reference ids included …
Juliusolsson05 Sep 27, 2026
b80a7fe
docs(git): plan the other consumers of a timed-out worktree list (#1430)
Juliusolsson05 Sep 27, 2026
ca5db47
fix(git): a timed-out worktree list is never read as 'no family' (#1430)
Juliusolsson05 Sep 27, 2026
74a5c0b
fix(agent-activity): an unresolved repository is recorded as Unknown,…
Juliusolsson05 Sep 27, 2026
8c59625
fix(user-mcp): bind each secret to the values of the inputs that stee…
Juliusolsson05 Sep 27, 2026
ae9135f
test(user-mcp): narrow the save result in the destination-guard test …
Juliusolsson05 Sep 27, 2026
c5cfb9b
docs(plan): settle lane listeners before probing, tag the probe (#1409)
Juliusolsson05 Sep 27, 2026
1d26861
test(browser-pocket): fail-first, short-lived lane listeners must nev…
Juliusolsson05 Sep 27, 2026
1ab5de5
fix(user-mcp): bind every secret to ALL other input values; agent cha…
Juliusolsson05 Sep 27, 2026
4aa0d8f
fix(browser-pocket): settle lane listeners before probing and tag the…
Juliusolsson05 Sep 27, 2026
d561ea8
docs(plan): record why serviceLanListener needs no test-side excuse (…
Juliusolsson05 Sep 27, 2026
9a7b7eb
fix(browser-pocket): time the settle window from observation; forget …
Juliusolsson05 Sep 27, 2026
d4d93a7
test(extensions): the LAN contract suite ignores the forwarded watche…
Juliusolsson05 Sep 27, 2026
fa83309
docs(plan): record review round 1 corrections and the escalated no-pr…
Juliusolsson05 Sep 27, 2026
f44c81f
fix(user-mcp): an agent's edit never deletes a secret; only the user'…
Juliusolsson05 Sep 27, 2026
d270f16
docs(browser-pocket): the window is measured from when the watcher sa…
Juliusolsson05 Sep 27, 2026
59c4822
fix(user-mcp): an agent can neither overwrite nor remove a withheld s…
Juliusolsson05 Sep 27, 2026
8b69ea6
docs(plan): record the q129 decision: mitigation, residual accepted, …
Juliusolsson05 Sep 27, 2026
909a6cb
test(user-mcp): narrow the saved id in the user-move deletion asserti…
Juliusolsson05 Sep 27, 2026
581d83c
docs(plan): the tall fixture is at the pinned pointer; the review gap…
Juliusolsson05 Sep 27, 2026
05b67fe
fix(user-mcp): a present blob that cannot be decrypted counts as with…
Juliusolsson05 Sep 27, 2026
619b3da
fix(git): #1430 review a/b: restart paging on a family change; hand t…
Juliusolsson05 Sep 27, 2026
fb61adf
fix(user-mcp): the withheld guard covers every blob on disk, includin…
Juliusolsson05 Sep 27, 2026
93ff416
test(git): pin the older-history timeout hand-off and its null guard;…
Juliusolsson05 Sep 27, 2026
9596a99
fix(history): replay a handed-over chunk on a cached catalog; older p…
Juliusolsson05 Sep 27, 2026
12d16c4
fix(browser-pocket): a scan from an obsolete plan writes no ages, pro…
Juliusolsson05 Sep 27, 2026
f5fc358
test(workspace): the Codex continuity harness carries worktreeReconci…
Juliusolsson05 Sep 27, 2026
6492db7
fix(history): an older page enters the reconciler as the OLDEST evide…
Juliusolsson05 Sep 27, 2026
3279b5b
Merge remote-tracking branch 'origin/main' into fix/worktree-timeout-…
Juliusolsson05 Sep 27, 2026
a6c56fb
fix(browser-pocket): stop() invalidates a scan in flight (#1452 round…
Juliusolsson05 Sep 27, 2026
17bde9e
fix(user-mcp): the agent guards compare input ids case-insensitively …
Juliusolsson05 Sep 27, 2026
33c6db5
chore(packages): bump the live-test credential and residue fixes (#12…
Juliusolsson05 Sep 27, 2026
e8d3b3e
chore(packages): workflow-mcp -> 513374d5 (#71, includes #63)
Juliusolsson05 Sep 27, 2026
6a0a151
chore(deps): bump opencode-terminal-headless to #11's merge (3935a3bb…
Juliusolsson05 Sep 27, 2026
d361428
Merge pull request #1354 from Juliusolsson05/chore/accept-codex-0157
Juliusolsson05 Sep 27, 2026
9cac182
Merge pull request #1397 from Juliusolsson05/fix/opencode-launch-with…
Juliusolsson05 Sep 27, 2026
b7a0051
Merge pull request #1420 from Juliusolsson05/fix/user-mcp-secret-order
Juliusolsson05 Sep 27, 2026
74536b6
Merge pull request #1450 from Juliusolsson05/fix/worktree-timeout-con…
Juliusolsson05 Sep 27, 2026
934ef0d
Merge pull request #1452 from Juliusolsson05/fix/lane-port-probe-test…
Juliusolsson05 Sep 27, 2026
d71c35a
Merge pull request #1463 from Juliusolsson05/chore/bump-live-test-cre…
Juliusolsson05 Sep 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/plans/2026-09-26-accept-codex-0157.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Accept Codex 0.157.1 (#234, codex-headless#16), with its known gaps filed

Size: short. This is a compatibility review; the only code change is the accepted version.

## Evidence (verified 2026-09-26)
- **Release notes, 0.149.1 → 0.157.1** (19 releases), scanned for rollouts, session/resume, the TUI composer and footer, approvals/trust, the Responses stream, proxy and config:
- 0.157.0 #47096 keeps TUI hints "immediately above the composer". The 0.157.1 PTY recording (`codex-headless testing/fixtures/composer-0157/tall-draft-ctrlc.json`) still shows the idle `? for shortcuts` hint on the row under the status row, which is what the composer classifier reads.
- 0.157.0 #47113 persists thread-creator identity in rollouts, and 0.153 #41912 persists token usage. Both are checked in the corpus below.
- 0.152/0.153 add rollout compression for shared lineages. None of the 1,850 files on disk is compressed.
- **Rollout corpus** (`~/.codex/sessions`, read line by line). The counts are a 2026-09-26 snapshot; the corpus grows while agents run (the reviewers counted 2,396–2,407 files, 219–233 of them 0.157.x). Record and payload types per minor version:
- The only top-level type in 0.150–0.157 not seen in 0.144–0.149 is `token_usage_record` (from 0.153). It is opaque to the parser's classifier: agent-transcript-parser#38.
- 0.157 writes no `event_msg:user_message`/`agent_message`. The prompt is a role-user `response_item` plus `event_msg:item_completed` (`UserMessage`). Fresh-rollout ownership (`codex-headless FreshRolloutClaim`) matches every durable user observation, including `response_item`, so it still finds the prompt.
- **Known gaps found in review.** Each was filed; all but #1289 are confirmed inherited from the already-accepted 0.149.1. **Status on 2026-09-27:** #1362, #1363 and #1289 are closed (#1363 by #1407). The two surviving prompt-extraction mutations from round-2 review b (the transcript reader's role-user `response_item`, and `foldCodexRecord`'s `response_item` branch) are both KILLED on origin/main `6dd23a49`. Disabling either fails a test there (16 transcript-reader tests; "lists prompts for a sampled rollout newest first"), so no new issue is needed:
- codex-headless#59: `listCodexSessions` summaries show the injected AGENTS/environment block (Agent Code's picker reads Codex's own index and is unaffected);
- codex-headless#62: the semantic tool lifecycle ignores `item_completed` (`CommandExecution`/`McpToolCall`), the only tool form since 0.149;
- #1362: `read_agent_transcript` drops `custom_tool_call` exec commands;
- #1363: the conversation source's no-index fallback reads only `event_msg:user_message`, so 0.157 prompts are lost when `state_N.sqlite` is unusable;
- #1289 (existing): 0.157 `compacted` payloads have no `type` and never render a compaction boundary;
- codex-headless#60: the recorded rollout-ownership tests run past 5 s under load.
- **Screen:** 0.157.0 and 0.157.1 raw PTY recordings pin the composer classifier: idle, a draft and Ctrl+C in 0.157.0; a 20-line draft in 0.157.1 (`tall-draft-ctrlc.json`, from codex-headless #57). This PR does not move the app's codex-headless pointer, but the pointer it already has (`d42cc1da`) contains both files, and `codexSession.nativeComposer.test.ts` replays the tall recording (round-2 reviews a and b).
- 0.157.0 #47178 **enabled the fullscreen transcript by default**. The 0.157.1 recording uses the alternate screen (`\x1b[?1049h`), so the classifier is pinned in that mode, and Agent Code passes no override.
- Trust and approval overlays have no 0.157 recording. They are unverified in the new default mode: a stated residual.
- **Runtime:** Agent Code has driven codex-cli 0.157.1 panes daily since 2026-09-25 (the #1319/#1327 work used them); the proxy recordings in `~/.config/agent-code/proxy` come from 0.157.1 sessions.

## Change
- `support/upstream-versions.json`: Codex `accepted` 0.130.0 → 0.157.1, with `checkedAt` 2026-09-26.
- The same in codex-headless (0.149.1 → 0.157.1), with notes pointing at this evidence.

## Out of scope
Cataloguing `token_usage_record` (agent-transcript-parser#38).
142 changes: 142 additions & 0 deletions docs/plans/2026-09-27-lane-port-probe-settle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Lane port watcher: stop probing short-lived test servers (#1409)

## Problem

`LanePortWatcher` probes, with one `GET /`, every TCP listener it finds in a
watched lane's process tree. It does this on the FIRST scan that sees the
listener (`LanePortWatcher.runScan` → `probeOnce` → `lanePortsIo.probe`).
Agents run test suites inside their lanes, so test suites' loopback servers
get an unsolicited request. Tests that count requests flake, only on developer
machines: CI has no watching app. #1187/#1406 is the confirmed case. The
runtime harness saw `GET /` with `user-agent: node` about 1.6 s after its
server started. #1406 excused "any `GET /`" in that one harness, and the
comment it left admits the residual: a runtime escape that requests exactly
`GET /` is excused too.

## Evidence this plan rests on

- **Every exposed listener in #1409's inventory binds port 0:**
- `serviceLanListener.test.ts:40`
- `proxy-harness.mts:136`
- `netFetch.test.ts:25,165`
- `netPolicy.test.ts:43`
- `playwrightActions.system.test.ts:18`
- `record-fixtures.mts:164`
- `runtimeHarness.ts:87`

They all live for one test or one harness run.
- **The recorded machine** (`__fixtures__/*.agents-and-tmux-terminal.*`,
replayed through `attributePorts`) has four attributed listeners:
- 4173 and 5292: vite, fixed ports;
- 62678: the recorder's own page server, `listen(0)`;
- 62679: the tmux pane's Python server, which is also ephemeral.

## Rejected directions

- **Skip ephemeral ports (≥ 49152 on macOS).** This would hide the recorded
tmux dev server (62679) and real tools that fall back to a random free port
(`serve` does, when 3000 is busy). It trades a test flake for a product
miss.
- **`HEAD` or another path.** It still reaches test handlers (#1409).
- **Read process command lines to spot test runners.** That undoes the
watcher's privacy rule: `ps` reads PID/PPID only.
- **Test-side excuses only.** They fix only the tests we know about. Every
future counting test inside a lane would rediscover the flake.

## Change

1. **Settle before probing (product).** The watcher records when it first saw
each `pid:port`. It probes, and lists, a listener only once it has SEEN it
listening for `PROBE_SETTLE_MS` (5 s), measured from the lsof answer that
first reported it. That is conservative: a server that was already up
before the watcher saw it still waits the full window. The first-seen entry is dropped when
the listener disappears, just as the probe cache already is.
- A test server that lives for less than the window is never contacted.
- A dev server's chip appears one or two scans later: about 6–9 s after
start at the 3 s scan floor, instead of about 0–3 s.
- Side benefit: short-lived test servers no longer flash chips in the lane
header.
- **UNCONFIRMED product call:** 5 s, and hiding a listener until it
settles. The alternative would be listing it unprobed as `other` at
once. That brings back the chip flicker, so I rejected it.
- Scheduling: while any listener is unsettled, the next scan runs at
`max(SCAN_FLOOR_MS, time until the earliest one settles)` instead of the
full back-off. Without that, a slow machine's 20 × back-off could delay
a chip for a minute.
2. **The probe identifies itself (product plus tests).**
- `lanePortsIo.probe` sends `User-Agent: AgentCode-LanePortProbe/1`, from
one exported constant (`LANE_PORT_PROBE_USER_AGENT`).
- A long-lived counting test that outlives the window can then excuse
exactly the probe, not "any `GET /`" or "any node fetch".
- A developer who sees the request in their server log can tell what sent
it.
3. **Tests that count requests (test side).** Each excuses exactly the probe's
User-Agent:
- `runtimeHarness.ts` is DEFERRED to a follow-up once #1436 merges. #1436
also edits that file, and #1406's `GET /` excuse already keeps the
harness green. The follow-up narrows the excuse to `GET /` AND the probe
UA, closing #1406's documented residual.
- `serviceLanListener.test.ts` is NOT changed (decided during the fix).
Each of its servers lives for one test, far under the window. The LAN
listener also forwards only an allow-list of headers
(`LAN_FORWARDED_HEADERS`, no `user-agent`), and that is correct product
behavior, so a forwarded probe could not be told apart upstream anyway.
The settle window is its fix.
- `scripts/proxy-harness.mts` (`requestCount`).

The latent listeners (netFetch/netPolicy, LanTransport/Cloudflared) count
nothing and stay unchanged.

## Test plan (fail-first from recorded data)

- New `LanePortWatcher` tests use the recorded listeners.
- The recorder's `listen(0)` page server (62678) appears in one scan and is
gone by the next. It must never be probed. On main it is probed on the
first scan, so this test fails there.
- A recorded dev server (4173) is not probed before the window and is
probed and listed after it.
- While a listener is unsettled, the next scan is scheduled for when it
settles, not after the back-off.
- Existing tests that expect a chip after one scan now advance the fake clock
past the window and scan again. Their assertions stay the same.
- A `lanePortsIo.probe` test against a real loopback server: the request
carries the probe UA.
- (Follow-up after #1436) runtimeHarness: an untagged `GET /` IS counted,
extending #1406's positive control.

## Review round 1 (a, b): what changed and what was corrected

- **Corrected claim (b):** the settle window is a MITIGATION, not a guarantee.
Test servers and dev servers both live for arbitrary lengths, and a stalled
run can hold a test server past 5 s. Counting tests therefore get
deterministic guards as well:
- `serviceLanListener.test.ts` DOES change, reversing the earlier decision
above. Its upstream ignores exactly `GET /`, and `send()` never uses `/`.
A probe forwarded through the LAN listener loses its User-Agent, so shape
is the only thing the upstream can check. A real-socket test replays that
sequence.
- `proxy-harness.mts` excuses `GET /` plus the UA, not the UA alone, so a
`POST /responses` carrying that UA is still counted and forwarded.
- **Window timing (a):**
- The age now starts when lsof returned the listener, not at scan start.
Otherwise a slow lsof, or a scan straddling a plan change, shortened the
window.
- An empty plan and `stop()` forget ages and probe answers.
- A failed lsof (timeout, signal, missing binary) throws instead of reading
as empty, so a settled chip is not pruned and hidden for another window.
- Known limit, documented at `PROBE_SETTLE_MS`: a close and rebind on the
same pid:port BETWEEN scans is invisible to sampling.
- **Escalated product alternative (b), OWNER/MANAGER DECISION:** stop
automatic HTTP probes entirely. Publish owned listening ports unverified,
and request a port only when the user clicks it or an agent opens it. That
gives zero unsolicited requests and immediate discovery. The costs: the
html/other classification the chip relies on (`LanePortChip` shows html
ports only), and non-page listeners shown as candidates, which the settle
window could still debounce. It is out of scope here: it changes what the
chip shows.

## Decision (B6 q129, owner proxy)

Accepted as a MITIGATION: the PR says `Refs #1409`, not `Fixes`. The settle
window's residual and the roughly 6–9 s chip latency are accepted. The passive,
probe-free discovery alternative is #1458.
51 changes: 51 additions & 0 deletions docs/plans/2026-09-27-opencode-launch-without-db-wait-bump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# App side of launching OpenCode without waiting on `opencode db path` (#1114)

Short plan: a pointer bump plus the host contract the package needs. The design, evidence and review history are in the package: `packages/opencode-terminal-headless/docs/plans/2026-09-27-launch-without-db-path-wait.md` (opencode-terminal-headless#10, merged `7a009541`).

## Outcome
An OpenCode pane's TUI spawns without waiting up to 20 s for the database-path lookup during a restore storm. The durable (committed-transcript) channel opens when the lookup lands. Nothing committed in the meantime is lost silently: it is proven absent or reported as a possible gap, which the renderer heals by re-reading history (#1117).

## Change
- `packages/opencode-terminal-headless` goes to `7a009541`. No lockfile change: the app resolves the package through a path alias, not a `file:` dependency, and the package's own dependencies did not change.
- **`OpencodeTerminalSession`: the host contract (recheck2 a/b).**
1. `tuiOutput` is latched in the PTY data subscription made right after spawn, and passed to the headless as `tuiOutputSeen`.
2. Terminal input (`write`: keystrokes, pastes) is **held** until the TUI's first output, then written in order. Programmatic prompts go through the server and the package gates them. So nothing that can make OpenCode commit reaches the PTY before it paints, and the package's "no output yet, nothing committed" proof holds. Held input for a TUI that never paints is dropped on stop.
- **Renderer test harness:** `AdapterPty` drops the data subscription it duplicated (the package's `FakePty` has it now, and the two private fields conflicted), and its launch-shape docs are refreshed.

## Tests
`opencodeTerminalSession.test.ts`, with the real headless recording its options. Each test fails on main's adapter:
- the latch is false until the first output, then true;
- input written before paint is held, then written in order, with later input passing through;
- held input is dropped when the pane stops.

## Verification boundary
Unit and system tests with fake PTYs and the real package. The app is not launched. The real TUI's "paint before reading input" ordering is the package's stated assumption (recorded sessions support it). Holding input makes the host side of it true by construction.

## Review round 1 and steering q97
- **b and c (blocker, package):** after a ladder recovery the late report fired before a BUSY-deferred reader positioned, so the app's one heal could run too early and a turn was lost. Fixed in the package (opencode-terminal-headless#11: the report comes from `onPositioned`). This PR bumps to that merge.
- **b (major, app): the pre-paint hold was unbounded, and exit did not clear it.**
- **Bounded:** 256 chunks (the renderer's own pre-attach queue cap) and 64 KiB. Past either bound the NEW input is refused, so what was accepted stays in order.
- **Refused, never silently dropped:** `OpencodeTerminalSession.write` returns `false`, `AgentSession.write` may return `false`, and `SessionManager.write` reports it. `AgentTerminalLeaf` shows a coalesced pane toast ("That input didn't reach the agent…") when `sendInput` answers `false`. The same toast now covers the older refusals keystrokes were silently dropped for (no backend, a prompt delivery holding the composer).
- **Cleared on exit** as well as on stop.
- Tests (each red on the previous head): a 64 KiB paste refused while earlier input is kept; the 257th chunk refused; exit clears the hold; the manager reports a refusal; the leaf toasts once.

## Steering q100: the pre-attach flush and neutral copy
- **The pre-attach flush ignored `sendInput` → false.** It is the pane's largest single write (up to 256 queued chunks), so it is the one most likely to exceed the bounded pre-paint hold, and it was dropped silently.
- It now goes through the same coalesced refusal reporter as the forwarder.
- **Ruling: a refusal is final, not retried.** A retry could land after newer keystrokes, out of order. The user is told which input failed: "What you typed while the terminal was attaching didn't reach the agent."
- **The forwarder's copy is neutral:** "That input didn't reach the agent." Main answers only a boolean (no backend, a delivery reservation, a full pre-paint hold), so naming any one cause would be false for the others.
- **Test:** a Submit queued before attach, with `sendInput` resolving false on flush, shows the flush message. Red with the flush reverted.

## Review round 1, reviewer a (MERGE-READY, minors)
- **P3 (package):** the reader's `onError` gate release was unpinned. Pinned in opencode-terminal-headless#11.
- **A3–A5 (app):** the per-spawn latch reset, the stop-time clear and the `onData` generation guard were unpinned. `start()` after `stop()` is allowed, so each is reachable.
- Pinned by a restart test: a fresh hold, and the dead PTY's late paint is ignored.
- Pinned by a stop test: the hold is empty after stop.
- Each test fails under its mutation.
- **Stale doc:** the `deliverPromptText` doc claimed a PTY paste. It now says HTTP, held until the durable reader positions.
- **Unbounded hold:** already bounded (q97).
- **A6 (latch set after the flush):** left alone, as a says; the flush is synchronous.

## Pointer bump to opencode-terminal-headless#11's merge (3935a3bb)
- The pointer moves from 7a009541 to 3935a3bb, the merge of #11, which contains 7a009541. That brings in round 1's blocker fix: the late database-path recovery is reported from `onPositioned`, so the app's one heal waits for the reader.
- The package's `package.json` and `package-lock.json` are unchanged between the two commits, so no lockfile resync is needed.
Loading
Loading