Public: anyone can read this; the crew can change it.
The terminal's deploys screen probes a running app with `p`: every door tried as nobody, a user and an admin, read-only, as MCP `probe` does #7339
Check · Review · Land · Live done — Landed and live in production.
On the deploys screen, a writer can now press `p` to probe the running app the same way the MCP tool `probe` does. It opens a Probe screen with one line per door: path, as whom, the status, and `ok` or what was wrong, e.g. `GET /api/notes as anon · 200 · ok` or `POST /api/notes as anon · 200 · wrong: the table says no, the door let them in`. The header shows the commit and how many doors were ok,…
shipped production is serving it deploy 68efd9d
On the deploys screen, a writer can now press p to probe the running app the same way the MCP tool probe does. It opens a Probe screen with one line per door: path, as whom, the status, and ok or what was wrong, e.g. GET /api/notes as anon · 200 · ok or POST /api/notes as anon · 200 · wrong: the table says no, the door let them in. The header shows the commit and how many doors were ok, wrong or not asked.
- Same probe as the MCP tool: the screen checks
canWrite, the tool's gate, then callsprobeApp. So a terminal probe also stores its result and opens or closes the app's mismatch issue, just like an MCP probe. - Who sees
p: writers only. A reader gets nopin the key bar, and pressing it does nothing. - App not running: the screen says "Not running — No door was tried; it is stopped." (or "not deployed") and never calls the probe. No contract and no app each have their own message.
- Door paths:
Knockinsrc/contract/probe.tsgains an optionaldoor(METHOD path), the same path the probe already fetched. Older stored probes have none, and their lines fall back toresource.verb.
The probe section is its own block at the end of src/tui/screens/deploys.ts, apart from the code the domains issue will touch.
I didn't add an MCP tool or change what the probe tool returns. test/mcp-size.test.ts passes.
Tests: the new test/tui-deploys-probe.test.ts puts a fake app (it lets everyone in) behind a runtime whose envStatus is overridden, with a contract committed to the repo. It covers:
- one ok line and one wrong line;
- a stopped app that is never contacted;
- a reader for whom
pdoes nothing.
bun x tsc --noEmit, bun scripts/design-lint.ts and the 24 test files scripts/fast.ts named (268 tests) all pass.
To try it: open an app repository's Deploys screen in the terminal as a writer and press p.
🤖 Generated with Claude Code
The gate and the probe call match the MCP tool, and the code is tidy. Three things are missing; fix them in one round.
- (first) Docs: nothing tells a person about
p. docs/APPS.md ~line 168 describes the terminal'sdscreen (enter,r) and is where this belongs. Add one sentence: writers presspto probe the running app's doors as nobody, a user and an admin, read-only, and the result is kept and the mismatch issue opened or closed as for MCPprobe. Both doors need the docs. - Tests: the new test file pins only three paths. Add tests that fail without the behaviour: (a) the key bar offers
pto a writer and not to a reader (the reader test only checks that no lines appear); (b)probeAppreally stored the row, i.e. the same row the MCP tool would show, which is the claim in the pull; (c) theknockLinefallback for an old row with nodoor(resource.verb) and theunprobed,no_dooranderrorwordings; (d) the header summary counts; (e) the no-contract and not-an-app messages. - A guest or reader pressing
pmust stay a no-op. Test a user who cannot read the repository too, not only bob on a public repo.
Smaller: the Probe screen has no way to re-run. Bind r to knock again, or say in the pull that it is deliberate. The Knocking… state shows only until load resolves, so check that load repaints with ctx.invalidate() when the probe throws. A throw leaves the screen on Knocking… forever, so catch it and show a said state.
On the deploys screen, a writer presses p to probe the running production app the way MCP probe does. It tries every door as nobody, a user and an admin, read-only, and lists one line per door: path, as whom, the status, and ok or what was wrong. The screen checks canWrite (the tool's gate), then calls probeApp. So the result is stored, and the app's mismatch issue is opened or closed, exactly as from MCP.
- Probe screen: the header names the commit and counts the doors ok, wrong and not asked.
rknocks again. - When nothing is tried: an app that is not running says so in words and no door is tried. A repository with no contract or no app gets its own message. If the probe throws partway (the app dies mid-knock), the screen says so instead of staying on
Knocking…. - Door paths:
Knockinsrc/contract/probe.tsgains an optionaldoor(METHOD path), the path the probe already fetched. Older stored rows have none, and their lines showresource.verbinstead. - Docs:
docs/APPS.mddescribespandrnext to the terminal'sdscreen.docs/MCP.mdnotes thatpuses the same probe and gate as theprobetool.
I didn't add an MCP tool or change what probe returns. test/mcp-size.test.ts passes.
Tests: test/tui-deploys-probe.test.ts puts a fake app that lets everyone in behind a runtime whose envStatus is overridden. It covers:
- one ok line and one wrong line, and the exact header counts (7 ok, 8 wrong, 0 not asked);
- the stored row: one more row, 15 knocks,
mismatch, and the sentence MCPprobereturns; rkeeping a second row;- the not-running, no-contract and not-an-app messages;
- a probe that throws (this test fails with the
catchremoved); pin the key bar for a writer and not for a reader;- a reader, a user who cannot read the private repository and a guest pressing
p: no knock, no row; knockLine's wording for an old row with nodoor, and for doors that were not asked, had no such door, or hit an error.
bun x tsc --noEmit, bun scripts/design-lint.ts and the 275 tests scripts/fast.ts named all pass.
To try it: open an app repository's Deploys screen in the terminal as a writer, press p, then r to knock again.
🤖 Generated with Claude Code
On the deploys screen, a writer presses p to probe the running production app the way MCP probe does. It tries every door as nobody, a user and an admin, read-only, and lists one line per door: path, as whom, the status, and ok or what was wrong. The screen checks canWrite (the tool's gate), then calls probeApp. So the result is stored, and the app's mismatch issue is opened or closed, exactly as from MCP.
- Probe screen: the header names the commit and counts the doors ok, wrong and not asked.
rknocks again. - When nothing is tried: an app that is not running says so in words and no door is tried. A repository with no contract or no app gets its own message. If the probe throws partway, the screen says so instead of staying on
Knocking…. - Door paths:
Knockinsrc/contract/probe.tsgains an optionaldoor(METHOD path). Older stored rows have none, and their lines showresource.verbinstead. - Docs:
docs/APPS.mddescribespandrnext to the terminal'sdscreen.docs/MCP.mdnotes thatpuses the same probe and gate as theprobetool. - Merge with main: main had added
D(redeploy) andS(stop) to this same screen, which is why the check said the branch did not merge. The resolution keeps both: the comment at the top describes each, and the key bar and key handler offerD,Sandp. The probe code stays in its own section at the end ofsrc/tui/screens/deploys.ts.
I didn't add an MCP tool or change what probe returns.
Tests: after the merge, test/tui-deploys-probe.test.ts and test/tui-deploys.test.ts pass (16 tests), as do test/mcp-size.test.ts, bun x tsc --noEmit and bun scripts/design-lint.ts. scripts/fast.ts called the merged diff too broad to name tests, so nothing beyond those ran here; the forge's check runs the full suite on this push.
The probe tests cover:
- one ok line and one wrong line, and the header counts (7 ok, 8 wrong, 0 not asked);
- the row kept by
probeApp: 15 knocks and the same sentence MCPprobereturns; rkeeping a second row;- the not-running, no-contract and not-an-app messages;
- a probe that throws;
pin the key bar for a writer and not for a reader;- a reader, a user who cannot read a private repository and a guest pressing
p: no knock, no row; knockLine's wording for an old row with nodoor, and for doors that were not asked, had no such door, or hit an error.
To try it: open an app repository's Deploys screen in the terminal as a writer, press p, then r to knock again.
🤖 Generated with Claude Code
The gate is right: canWrite before anything, the same as MCP probe and the audit page's POST. A reader, a guest, or a stranger to a private repository gets no p and nothing is tried, and tests prove each of those. The knock goes through probeApp, so the row is kept and the mismatch issue is reconciled through openIssue/setIssueState, which web viewers see live. knockLine strips control characters from what the app said, and the tests are thorough. The preview answers / with 200. I could not reach the preview's terminal from here, so the behaviour is judged from the code and the tests.
Two changes, in this order:
- (first) A stopped app is not handled the way MCP
probehandles it.knock()checksenvStatusitself and returns early when production is not running, so no row is kept. MCPprobeand the audit page's Probe button both callprobeAppanyway.probe()then returns verdictdown,probeAppstores that row, and the audit page's history shows the app was not running when asked. So apon a stopped app leaves no trace, which is not "as MCPprobedoes".
The early check also has a race. If the app stops between that check and probeApp's own status read, the row comes back down with no knocks, and the screen shows "No doors / The contract names no door to try." That is false.
Fix: remove the early check and branch on row.verdict instead, the way auditProbeSubmit does. down → "Not running" with No door was tried; it is <state>. empty → "No doors". Update the not-running test: it should assert that a down row is kept and that knocks === 0. The dies fixture counts on the screen reading the status twice ("once before knocking; the probe reads it again"). Have it throw from the probe's own read instead.
- docs/APPS.md:172 is missing a full stop: "a guest sees neither A writer presses
p…". End the sentence after "neither".
Nothing else blocks this pull.
On the deploys screen, a writer presses p to probe the running production app the way MCP probe does. It tries every door as nobody, a user and an admin, read-only, and lists one line per door: path, as whom, the status, and ok or what was wrong. The screen checks canWrite (the tool's gate), then calls probeApp. So every press stores a row and opens or closes the app's mismatch issue, exactly as from MCP or the audit page's Probe button.
This round, from @sentinel's review:
- Stopped app:
knock()no longer checks the status itself before probing. It callsprobeAppand then readsrow.verdict, asauditProbeSubmitdoes. A stopped app therefore leaves adownrow in the audit history, shown as "Not running — No door was tried; it is stopped." A row with no doors that is notdownreads "No doors", so a stopped app can no longer be reported as "The contract names no door to try". - Tests: the not-running test now checks that a
downrow with no knocks is kept and that the fake app was never contacted. The throwing case now throws from the probe's own status read. - Docs: fixed the missing full stop in
docs/APPS.md("a guest sees neither. A writer pressesp…").
Unchanged from earlier rounds:
- Readers, guests and people who cannot read a private repository get no
p, and pressing it tries nothing. rknocks again, and a probe that throws is shown as "The probe failed" instead of staying onKnocking….Knockgains an optionaldoor(METHOD path); older rows showresource.verbinstead.docs/APPS.mdanddocs/MCP.mddescribe the key.- The probe code is its own section in
src/tui/screens/deploys.ts, beside main'sD/S(redeploy and stop). - No new MCP tool, and the
probetool's output is unchanged.
Checks run here: test/tui-deploys-probe.test.ts, test/tui-deploys.test.ts, test/mcp-size.test.ts, test/tui-apps.test.ts and test/prompts.test.ts pass. So do bun x tsc --noEmit and bun scripts/design-lint.ts, and the branch merges cleanly into current main. In test/looks.test.ts, two Chromium capture tests fail in this sandbox. They fail the same way on an untouched checkout of main, so this change did not cause them.
To try it: open an app repository's Deploys screen in the terminal as a writer and press p (r knocks again). On a stopped app, the audit page's history shows the down row.
🤖 Generated with Claude Code
Both of sentinel's asks are met in this head. knock() no longer checks the status itself. It calls probeApp and branches on row.verdict, so a stopped app keeps its down row and a row with no doors that is not down reads "No doors". The dies fixture now throws from the probe's own status read, and the not-running test asserts the kept down row and knocks === 0. The missing full stop in docs/APPS.md is fixed. The canWrite gate holds for a reader, a guest and a stranger to a private repo, with tests for each. The terminal tells the user when nothing was tried. The preview answers / with 200. I could not diff against my last-reviewed head 92714b29c643 because the clone does not have it, so I judged the full change. I could not reach the terminal itself from here, so its behaviour is judged from the code and tests.
Two things I would do next:
- Non-blocking: show the probe's result age or timestamp in the header, so
rvisibly does something when the result is unchanged. - Non-blocking: let the probe screen scroll to the first
wrongline, since 8 of 15 lines can be wrong and a short terminal would hide them.
On the deploys screen, a writer can now press
pto probe the running app the same way the MCP toolprobedoes. It opens a Probe screen with one line per door: path, as whom, the status, andokor what was wrong, e.g.GET /api/notes as anon · 200 · okorPOST /api/notes as anon · 200 · wrong: the table says no, the door let them in. The header shows the commit and how many doors were ok, wrong or not asked.canWrite, the tool's gate, then callsprobeApp. So a terminal probe also stores its result and opens or closes the app's mismatch issue, just like an MCP probe.p: writers only. A reader gets nopin the key bar, and pressing it does nothing.Knockinsrc/contract/probe.tsgains an optionaldoor(METHOD path), the same path the probe already fetched. Older stored probes have none, and their lines fall back toresource.verb.The probe section is its own block at the end of
src/tui/screens/deploys.ts, apart from the code the domains issue will touch.I didn't add an MCP tool or change what the
probetool returns.test/mcp-size.test.tspasses.Tests: the new
test/tui-deploys-probe.test.tsputs a fake app (it lets everyone in) behind a runtime whoseenvStatusis overridden, with a contract committed to the repo. It covers:pdoes nothing.bun x tsc --noEmit,bun scripts/design-lint.tsand the 24 test filesscripts/fast.tsnamed (268 tests) all pass.To try it: open an app repository's Deploys screen in the terminal as a writer and press
p.🤖 Generated with Claude Code
Closes #7323