feat(cli): keel open — the way back into a console nobody watched start (#756) - #759
Open
eaitbrahim wants to merge 1 commit into
Open
feat(cli): keel open — the way back into a console nobody watched start (#756)#759eaitbrahim wants to merge 1 commit into
eaitbrahim wants to merge 1 commit into
Conversation
…art (#756) `keel serve` prints its URL once, with the session token in it. That is enough when a human is watching and useless when nothing is: under launchd the line goes to StandardOutPath, and the route back into your own console becomes grepping a log for a token. Step 4 of #756 -- the four serve plists -- is unusable without this, which is why it comes first. THE TOKEN GOES TO DISK, AND THAT IS BOUNDED BY ONE RULE `security.py` layer 3 rests on the token being ephemeral, and `serve` PRINTS the claim every run: "the token is new every run and is never written to disk". A lockfile makes that sentence false, so it is written under one condition: THE RECORD EXISTS ONLY WHEN THE URL IS NOT BEING SHOWN TO A HUMAN. `sys.stdout.isatty()` decides. Attached to a terminal nothing is written, the posture is byte-for-byte what it was, and the sentence is still printed because it is still true. Detached, a DIFFERENT sentence is printed -- naming the file, its mode, and `keel open` -- because telling an operator their live credential is not on disk while writing it there is the class of false safety assurance `_session_banner` exists to refuse. The argument is not that persisting a token is fine. It is that on the detached path the token is ALREADY in StandardOutPath at the daemon's umask, so the real choice is between that log line and a 0600 file in a 0700 directory that is deleted on shutdown and checked for staleness before it is offered. The second is strictly less exposure, and it is the only one that makes unattended serve usable. `security.py`'s layer 3 now says which of the two runs it is describing. THE FILE IS A HINT, NEVER THE AUTHORITY `forget` covers the clean stop. A SIGKILL or a power cut does not, so `live_record` re-checks the pid with signal 0 before offering anything -- handing over a URL that refuses the browser would read as keel being broken rather than as keel being stopped. `pid <= 0` is refused before the call, because `os.kill(0, 0)` signals the process GROUP and would report a bad record as live. Written through a temporary and renamed, so no reader sees a half-written record. Corrupt JSON reads as absent rather than raising: `keel open` must not traceback at an operator whose server just died. One file per PORT, because #756 counts four deployments and one shared file would make the last server to start the only reachable one. The token is never in the filename -- a listing is readable by anything that can list the directory, even when the file is not. THREE ABSENCES, THREE SENTENCES No server, a server started from a terminal, and a server that was killed look identical from here. "not found" would send an operator hunting for a bug in the two cases where nothing is wrong, so each gets its own message and the interactive one names the terminal that has the URL. 6,352 passed / 3 skipped, ruff and mypy clean. Four mutants killed: the record written while a human watches, the record never forgotten, the liveness check skipped, and the file at 0644. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZZxmspQXe5qJ9FAsG13s6
Comment on lines
+102
to
+110
| json.dumps( | ||
| { | ||
| "pid": os.getpid(), | ||
| "host": host, | ||
| "port": int(port), | ||
| "token": token, | ||
| "started_ts": int(time.time()), | ||
| } | ||
| ) |
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.
Step 3 of #756. Step 4 — the four
com.keel.serve.*.plistfiles — is unusable without it, which is why it comes first.security.pylayer 3 rests on the token being ephemeral, andserveprints the claim every run: "the token is new every run and is never written to disk." A lockfile makes that sentence false. So it is written under exactly one condition:sys.stdout.isatty()decides.keel open. Telling an operator their live credential is not on disk while writing it there is the class of false safety assurance_session_bannerexists to refuse.The argument is not that persisting a token is fine. It is that on the detached path the token is already in
StandardOutPathat the daemon's umask. The real choice is between that log line and a0600file in a0700directory, deleted on shutdown and checked for staleness before it is offered. The second is strictly less exposure, and it is the only one that makes unattendedserveusable.security.pylayer 3 now says which of the two runs it is describing.If you'd rather have uniform behaviour — always write, or never write and drop
keel open— that is a one-line change and I'd rather you made it deliberately than inherited mine.The file is a hint, never the authority
forgetcovers the clean stop; aSIGKILLor power cut does not.live_recordre-checks the pid with signal 0 before offering anything — handing over a URL that refuses the browser reads as keel being broken rather than stopped.pid <= 0is refused before the call, becauseos.kill(0, 0)signals the process group and would report a bad record as live.Written through a temporary and renamed, so no reader sees a half-written record. Corrupt JSON reads as absent rather than raising. One file per port — #756 counts four deployments, and one shared file would make the last server to start the only reachable one. The token is never in the filename: a listing is readable by anything that can list the directory, even when the file is not.
Three absences, three sentences
No server, a server started from a terminal, and a server that was killed look identical from here.
"not found"would send an operator hunting for a bug in the two cases where nothing is wrong, so each gets its own message — and the interactive one names the terminal that has the URL.Checks
6,352 passed / 3 skipped, ruff and mypy clean. Four mutants killed: record written while a human watches, record never forgotten, liveness check skipped, file at
0644.Not covered here: that
launchdactually produces a non-tty stdout on your machine. That is step 4's first observation.🤖 Generated with Claude Code
https://claude.ai/code/session_01KZZxmspQXe5qJ9FAsG13s6