feat(console): the refusal now says how to GET the address it asks you to paste - #810
Merged
Merged
Conversation
…u to paste The view named the cause well and the remedy not at all. On 2026-09-16 two people who know this codebase were locked out by a daemon restart and neither recovered from the screen: one went to `keel open --help`, the other asked a colleague. Worse than an omission, the refusal's prose says the token "is never written to disk". Since #756 that holds only for a server attached to a TERMINAL -- run detached, under launchd as every console on that machine is, `runtime.py` records the address in a 0600 file precisely so `keel open` can hand it back. A reader who believes the sentence concludes the token is unrecoverable and restarts the server, minting a NEW one -- the exact move `refusedView`'s own docstring warns against. `keel open` was already named in `server.py`, in the launchd plist ("Do not grep the log for it") and in `runtime.py`'s module docstring. Everywhere except the screen the locked-out operator is looking at. Added above the paste field, because the command PRODUCES the value the field consumes, and as a boxed `user-select: all` code block rather than a sixth sentence in a paragraph that already runs five. The port comes from `window.location` -- the same source and the same reason as `timelineExportUrl` -- and nothing else about the deployment is named. The path in particular: the port is the one fact the reader demonstrably already has, so naming it discloses nothing to a request that was NOT admitted, which starts to matter once #648 lets this console answer beyond loopback. `openCommandNode` returns a NODE and joins nothing: this file may use neither template literals nor `+`, since `test_render_contains_no_arithmetic` bans the operator outright rather than try to tell string concatenation from money. The empty-string port of a default-port URL takes the bare command, which `keel open` handles itself. Six tests, each naming the mutation it rejects; verified by mutation that dropping the command, hardcoding the port, and reverting the label each fail a different count. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AoGRoExgHVCsWDHT3ej8mD
This was referenced Sep 24, 2026
eaitbrahim
added a commit
that referenced
this pull request
Sep 25, 2026
…regression) (#817) #810 added `const note = el("p")` to refusedView beside the existing `const note = el("p", "detail")`. A duplicate declaration in one scope is a SyntaxError for the whole module, so render.js did not load and every console built from main showed a blank shell. The first is now `howNote`. Nothing caught it because the repo has no JavaScript toolchain. A new scan in test_client_assets fails any module that declares one name twice in one block scope, with a self-test that it catches the #810 shape and passes sibling blocks and loop heads. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
eaitbrahim
added a commit
that referenced
this pull request
Sep 25, 2026
…e switcher with no tokens (#818) MINOR: #810 and #814/#816 add console features. No schema change since 0.16.0, so a deployment needs no `keel migrate` step beyond the usual idempotent run. What lands: #807 -- a quote-increment cache record predating quote_increment is a MISS, not an "unknown". #810 -- the refused view says how to get the address it asks you to paste; #817 fixes its duplicate `const note`, which blanked the console. #813 (#812) -- the cycle line ends `vetoed=N (rail, ...)`, so a rail veto no longer reads as a silent drop. #815 + #816 (#814) -- a console switcher beside the session chip. /api/config carries no session token; /switch/<port> redirects into the chosen console's own hand-off, local deployments only. Eight workspace versions and the inter-package `==` pins move together, plus the refreshed uv.lock the release workflow checks first. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What the operator saw
Cause named well. Remedy named nowhere.
Two failures, and the second is the bad one
1.
keel openis never mentioned. It is named inserver.py:1125, in the launchd plist("This process prints its URL... where nobody is reading. Do not grep the log for it. Run
keel's open command"), and in
runtime.py's own module docstring — everywhere except the screenthe locked-out operator is looking at.
2. "never written to disk" is no longer true, and it is the sentence that closes the door.
Since #756, that holds only for a server attached to a TERMINAL. Detached — launchd, a pipe, a
container —
runtime.pyrecords the address in a0600file precisely sokeel opencan handit back. A reader who believes the sentence concludes the token is unrecoverable and restarts
the server, which mints a NEW one.
refusedView's own docstring already warns about that move:"sends them to restart the thing that is working… and makes the situation worse."
Evidence it fails in practice: on 2026-09-16 two people who know this codebase were locked out by
a daemon restart. One went to
keel open --help; the other asked a colleague.The change
An instruction block above the paste field — the command produces the value the field
consumes, so meeting the field first means being asked for something you do not have:
The field's label moves from "Paste the address keel printed" — past tense about something a
daemon wrote to a log the plist says not to grep — to "Paste the address from that command".
Boxed, monospaced,
user-select: all, because its whole purpose is to be copied. The detailparagraph beside it runs five sentences; a command appended as a sixth is a command nobody sees.
Two constraints worth noting
The port comes from
window.location, the same source and stated reason astimelineExportUrland the deployment card: a fact about where this page is, not a claim from apayload that just refused us. Nothing else about the deployment is named — the path in
particular. The port is the one fact the reader demonstrably already has (they reached this page
on it), so naming it discloses nothing to a request that was not admitted. That begins to matter
once #648 lets this console answer beyond loopback.
openCommandNodereturns a node and joins nothing.render.jsmay use neither templateliterals nor
+—test_render_contains_no_arithmeticbans the operator outright rather thantry to tell string concatenation from money, and my first attempt tripped exactly that gate.
appendtakes several strings, so the port is placed beside the command instead of joined to it.A default-port URL gives
location.portas the empty string and takes the bare command, whichkeel opendefaults correctly.Verification
Six tests, each naming the mutation it rejects, asserted on code with comments stripped so the
prose explaining the change cannot stand in for the change. Mutation-verified:
Not changed: the server-side 403 detail text still carries the stale "never written to disk"
sentence for non-browser clients. Worth a follow-up; this PR is the HTML view only.
🤖 Generated with Claude Code
https://claude.ai/code/session_01AoGRoExgHVCsWDHT3ej8mD