Repository navigation
Claude Code signed in from chat: a card with Anthropic's link and its code for the computer (FIN-353) - #920
Conversation
… code for the computer (FIN-353) Signed out (`claude auth status`, before a session's process starts), a Claude agent's turn asks its person on a card: `claude auth login`, run with no display, says Anthropic's sign-in URL, which the card links for their own browser; the code that page shows goes back on the card as an answer for the computer (#911), never a record, and into the login's input. A code it calls invalid is asked again (at most 3); a Stop, a let-go or the card's expiry ends the turn and its login; a restart ends it too (its verifier lived only in that process), and the next message asks anew. A sign-in after an install is never waited on by a hold. The stub image's Claude Code starts signed out, so the chat e2e lane signs in from the card (screenshots for FIN-353). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alexlwn123
left a comment
There was a problem hiding this comment.
Fail for now at 8f9b887059f5b4aba35215508f6f4faaa2550559, reviewed against #911 at 43cca3b6c1972f06bd75356fca82a7b60898a6af.
This layer adds Claude sign-in through the existing computer-answer card. That reuse keeps the scope appropriate, but two subprocess-boundary failures need fixing.
Blocking:
-
Wait for a complete sign-in URL before publishing the card (
images/bridge/src/runtime/claude.rs:734–740). The reader emits arbitrary 1024-byte chunks, butsplit_whitespace()treats the current buffer's trailing fragment as a complete URL. When a read ends inside the URL, the card permanently gets a truncated OAuth link. I reproduced this againstsign_inwith a disposable child writinghttps://claude.com/cai/oauth/authorize?client_id=then, 100 ms later,test&state=complete: the card contained only the first part. Pipe read boundaries are not message boundaries. Buffer each stream until the URL token is terminated (or that stream reaches EOF); test splits inside the scheme, path and query, including the read-buffer boundary. -
Honor cancellation throughout sign-in, including the code exchange (
images/bridge/src/runtime/claude.rs:772–798). After a code is submitted, this select never receivesToSession::StoporForget. A slow exchange therefore keeps running after Stop; successful completion returnsDoneand the caller invokesbeginbefore processing the queued cancellation, allowing the original request to start after cancellation. I reproduced this with a child that exits successfully 300 ms after receiving the code and a Stop sent at 50 ms:sign_inreturned success instead ofOutcome::Stopped. The initial auth-status check, URL wait and final auth-status check have the same cancellation gap. Keep Stop/Forget/channel closure observable across those phases and kill the login group before returning; cover cancellation before the URL, during exchange, and during final status verification, asserting that no session begins afterward.
Validation: both temporary regression tests failed on this head; removed them afterward. The existing Claude integration suite had 15 passes and 3 failures caused by its Linux /proc process-count assertions on this macOS host, not attributed to this PR. GitHub check and images check passed; real-Hermes Docker was still running. Real-Claude Docker and browser e2e were not run locally. The required external engineering-style file was unavailable at its documented path.
…sked again, a Stop ends its login After the install, rowan's turn asks to sign in on a card linking Anthropic's sign-in as the real `claude auth login` says it; a code that is not one is called invalid and asked again; a Stop ends the turn and leaves no login running. The same card after a restart and after an image update. By name only (FRAGMENT_DOCKER_REAL_CLAUDE=1): no model is called and no real code is used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alexlwn123
left a comment
There was a problem hiding this comment.
Fail for now at 7fce8a7968eb4a26d755c442bc2effa33b65da64, against #911 at 43cca3b6c1972f06bd75356fca82a7b60898a6af.
Both blockers from my previous review remain. The runtime implementation and Claude integration tests are unchanged from that reviewed head; the added real-Claude Docker coverage exercises Stop while awaiting a card answer, not cancellation during the exchange.
Blocking:
-
Partial OAuth URLs still become permanent card links (
images/bridge/src/runtime/claude.rs:734–740). The readers deliver arbitrary 1024-byte chunks, andsplit_whitespace()accepts an unterminated trailing URL fragment. A pipe read ending inside the URL publishes a truncated link before the remainder arrives. Preserve stream boundaries and wait for a terminated URL token (or EOF). Cover splits inside the scheme, path and query, including the buffer boundary. -
Cancellation still cannot interrupt the code exchange (
images/bridge/src/runtime/claude.rs:772–798). After submitting the code, the select observes process exit, output and timeout, but never Stop/Forget/channel closure. A Stop during a slow successful exchange remains queued whilesign_inreturns Done and its caller invokesbegin, starting the original request after cancellation. The initial status check, URL wait and final status verification likewise do not observe cancellation. Make cancellation observable throughout these phases, terminate the login group, and test that cancellation before the URL, during exchange and during final verification prevents session start.
The existing computer-answer path remains an appropriate, contained implementation choice; no additional abstraction is needed to fix these boundaries.
Validation: inspected the current head and the changes since my previous verdict, and traced both unchanged failure paths and their callers. The earlier review records reproductions; I did not rerun those tests in this pass. Current GitHub check, images check and real-Hermes Docker are pending. Local Docker/browser suites were not run; the external engineering-style file is unavailable at its documented path.
… waits (Alex on #920) 1. The login's output is kept per stream, and its URL taken only once whitespace (or the stream's end) follows it: a pipe's reads are not its lines. Tested at every split point of the real login's words; fake-claude now says its URL in two writes, apart. 2. Every wait of a sign-in (the first `auth status`, the URL, a code's exchange, the final `auth status`) runs under `uncut`: a Stop or a let-go already waiting wins, the login's process group is killed, and no session begins. Tested with fake-claude slowed in each phase: the turn ends stopped, no login is left, and Claude Code first hears the next message. The Stop test now waits for the tool's call before its Stop: a Stop in the sign-in check is honoured before Claude Code starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alexlwn123
left a comment
There was a problem hiding this comment.
Pass for this stack layer at dfeb5f5c2717df8f3b170c308b92786356d8779c, against #911 at 43cca3b6c1972f06bd75356fca82a7b60898a6af.
Both earlier blockers are fixed: each output stream now buffers the OAuth URL until whitespace or EOF, and uncut observes Stop/Forget/channel closure during the initial status check, URL wait, code exchange and final status check. Cancellation returns without starting the requested session and drops the active login's process-group guard.
The fixes stay within the image runtime and reuse the existing computer-answer path. The small shared cancellation helper is appropriate for these waits; I found no new blocking regression in the changes since my previous verdict.
Validation: the URL test covering every split point passed locally. The sign-in integration test reached its card but failed at its Linux /proc process-count assertion on this macOS host (claude.rs:448); this does not establish a runtime failure. The new cancellation integration test also relies on /proc, so I inspected it rather than running it here. GitHub check, images check and real-Hermes Docker are still pending. Docker/browser e2e were not run locally, and the external engineering-style file is unavailable at its documented path.
|
Thanks, Alex. Both points were real. Fixed at dfeb5f5.
Also since your review: after an install, the session drops its hold mark before the sign-in waits, so a save is never held by a card ( Validation: On macOS: the three |
alexlwn123
left a comment
There was a problem hiding this comment.
Pass for this stack layer at b225a9eadca42f0e0b6851dcf67a9b5e06d12c43, against #911 at 104558d850f43b590dd18ad23c6e654848b2e841.
Both earlier blockers remain fixed: OAuth URLs wait for a complete token per output stream, and cancellation is observed through the status checks, URL wait and code exchange. This head merges the updated base; the Claude runtime, sign-in tests and chat e2e code are unchanged from my approved head dfeb5f5c2717df8f3b170c308b92786356d8779c. I checked the merged context and found no new blocking regression.
The layer remains appropriately confined to the image runtime and reuses the existing computer-answer card path; the merge adds no sign-in machinery.
Validation: the URL test covering every split point passed locally on this head. GitHub check, images check and real-Hermes Docker are still pending. Linux /proc-dependent integration tests and Docker/browser e2e were not rerun on this Mac. The external engineering-style file remains unavailable at its documented path.
alexlwn123
left a comment
There was a problem hiding this comment.
Pass for this stack layer at 7fef814bba04d9201b064a9978434bb0784dd819, against #911 at 8c6d32ec00d3d0df002d54f13b9262221ec63f67.
Both earlier blockers remain fixed: OAuth URLs wait for a complete token per stream, and cancellation is observed through the status checks, URL wait and code exchange. The Claude runtime, sign-in integration tests and fake Claude are unchanged from my approved head 95084b0d84670d1c8a68dcef5d2c79e8bec75cc1. I checked the merged card path, including the base's script command rename to code-card, and found no new blocking regression.
The layer continues to reuse the generic computer-answer path and confines vendor-specific behavior to the image; this merge adds no sign-in machinery.
Validation: the complete-URL unit test and a_cards_answer_for_the_computer_reaches_its_runtime_alone integration test both passed locally on this head. GitHub check, images check and real-Hermes Docker remain pending. Linux /proc-dependent sign-in integration tests and Docker/browser e2e were not rerun on this Mac. The external engineering-style file is unavailable at its documented path.
FIN-353, PR 3 of 3 (Paul: "(a) first message, (b) inside the card"). Stacked on #911, which adds the generic answer-for-the-computer card field; GitHub retargets this PR to master once #911 merges.
What
When a Claude agent's Claude Code is signed out, its turn asks its person to sign in on a card:
claudeprocess starts, the bridge runsclaude auth status. If no one is signed in, it startsclaude auth loginwith no display (so it opens no browser on the computer). The login prints Anthropic's sign-in URL, and the card links it for the person's own browser on any device: "Claude Code needs you to sign in to your Claude account. Open the sign-in in your own browser, then paste the code it shows you here."/answer→ the bridge. It is written to the login's input and is never a record or a log line.auth statusagrees. The turn says "Signed in to Claude", then does what was asked.LateCardpath), the session drops its hold mark before the sign-in waits. A save's hold is never stuck behind a person reading their email.Tests
tests/claude.rs:a_turn_asks_its_person_to_sign_in_and_runs_once_signed_in(valid, invalid code asked again, no record holds either code, replay asks nothing);a_sign_in_ends_with_its_turn_and_the_next_message_asks_anew(restart: login killed, old card's code refused with words, next message signs in; invalid: 3 wrong codes end the turn; a Stop ends it; no login left running);an_install_then_a_sign_in_which_no_hold_waits_on(a hold during the sign-in is answered at once; signs in after; replay asks neither).a_stop_anywhere_in_a_sign_in_ends_it_and_no_session_begins(Alex): a Stop before the URL, during a code's exchange, and during the final status check each end the turn stopped, leave no login, and Claude Code first hears the next message;a_sign_in_url_is_taken_only_once_said_whole(Alex): the URL is taken only once said whole, at every split point of the real login's words.claude_install: now in the shell. Install card → Install → sign-in card (link, masked field) → wrong code asked again → right code → "Signed in to Claude" + the reply; no record holds either code.cargo test --workspaceand clippy-D warningsinimages/;cargo xtask checkon the merged head.framing_shell(the fresh chat's frame never got its hello). That is the known local-onlyERR_NETWORK_CHANGEDmodule stall: this box still hasdisable_ipv6=0, which CI sets since ci: e2e shards give new links no IPv6, so Chrome sees no network change (the module stalls) #796. Its later imports never reached the node while the computer churned for the new agent.tests/docker.rsreal-Claude test, run by name (FRAGMENT_DOCKER_REAL_CLAUDE=1, no model call, no real code): passed locally against Docker lane: a Claude agent's first turn asks to install (after #901) #913's version. Anthropic's installer runs (46 s). rowan's turn shows the sign-in card linking the realclaude auth loginURL (claude.com/cai/oauth/authorize, PKCE S256). The real login calls a fake code invalid, and the card asks again. A Stop ends the turn and leaves no login running. The same card appears after a restart and after an image update.Complexity ledger
sign_in,ensure_signed_in,uncut(one cancel guard for every wait) andsign_in_url(~230 lines) inruntime/claude.rs, three constants and the card's words;auth statusandauth login(~45 lines);as_personnow builds the session's environment for bothspawnand the login (−9 lines inspawn);claude auth statusprocess per session process start: about 70–100 ms (Claude Code 2.1.314, measured locally).Screenshots of the real cards go on FIN-353 before auto-merge.
🤖 Generated with Claude Code