From 0013687978669ec53c565d70ac91a8504ab9fcb5 Mon Sep 17 00:00:00 2001 From: Lexus2016 Date: Sat, 3 Oct 2026 14:23:35 +0200 Subject: [PATCH] fix(engine): the Subscription engine starts where `claude` was never opened MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on CLI 2.1.288 with an empty HOME: an interactive `claude` stops on up to three screens before its input box — the theme picker, "do you trust this folder?" and the Bypass Permissions warning. None is numbered, so the #20 detector missed all three, and two open with the caret on "No, exit": the engine pasted the user's message, Enter quit `claude`, and the chat said only "failed to start" or "went idle". Every fresh Docker container hit it; a third-party image pre-seeds the keys in its entrypoint to work around it. - The answers are the consent `claude -p` (the default engine) never asks for. The warning is skipped with `--settings {"skipDangerousModePermissionPrompt":true}`, no file written. Theme and trust have no flag, so claude-firstrun.js writes hasCompletedOnboarding / theme / projects[].hasTrustDialogAccepted into the CLI's global config, only when missing, for the exact spawn directory only (the CLI's ancestor rule stops at git roots — measured — and a worktree is one), under the raw and NFC key as the CLI itself does. - The write keeps a symlinked config's link and refuses a dangling one, refuses an empty or unparsable file, preserves mode and owner, re-reads before the rename and re-plans if a live claude saved meanwhile, and on a single-file bind mount rewrites in place from a uniquely named backup with a full-write loop and fsync. - paneAwaitingSelection() recognises the unnumbered select structurally, and both detector branches consult one definition of the input box (a rule-framed region whose first line is the caret) so a draft typed into the box is never a widget. A real AskUserQuestion capture is rule-framed too, which is why "between two rules" alone would have hidden it. - A screen still up after the startup wait is reported with its excerpt instead of pasted into, and Stop during the wait returns before the paste. - HOME / CLAUDE_CONFIG_DIR (resolved absolute) are pinned in the pane command: the pane inherits the tmux SERVER's env, which outlives a studio restart. Two adversarial review passes (17 findings) shaped the final version; each fix is pinned in test/claude-firstrun.test.js (48 checks, real screens as fixtures, runInteractiveSingle driven against a fake `claude`), and every pin fails when its fix is reverted. --- CLAUDE.md | 73 ++++++- claude-firstrun.js | 209 ++++++++++++++++++ claude-interactive.js | 127 +++++++++-- package.json | 2 +- test/claude-firstrun.test.js | 215 +++++++++++++++++++ test/fixtures/claude-tui/askuserquestion.txt | 40 ++++ test/fixtures/claude-tui/firstrun-bypass.txt | 40 ++++ test/fixtures/claude-tui/firstrun-ready.txt | 40 ++++ test/fixtures/claude-tui/firstrun-theme.txt | 40 ++++ test/fixtures/claude-tui/firstrun-trust.txt | 40 ++++ 10 files changed, 805 insertions(+), 21 deletions(-) create mode 100644 claude-firstrun.js create mode 100644 test/claude-firstrun.test.js create mode 100644 test/fixtures/claude-tui/askuserquestion.txt create mode 100644 test/fixtures/claude-tui/firstrun-bypass.txt create mode 100644 test/fixtures/claude-tui/firstrun-ready.txt create mode 100644 test/fixtures/claude-tui/firstrun-theme.txt create mode 100644 test/fixtures/claude-tui/firstrun-trust.txt diff --git a/CLAUDE.md b/CLAUDE.md index 875a9b8..ac22cd1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,7 +31,7 @@ npm run release # bump, commit, tag, push → GitHub npm run release:mac # on the Mac with the cert: build, notarize, verify, upload, bump the cask ``` -No linting and no build step configured. `npm test` chains 88 test files under `test/`: 19 DOM-less render/UI-logic tests (`test/render/*.test.mjs`, run through `node --test`) plus 69 plain-`node` suites in `test/` covering the overload detector, env load order, multi-agent results, terminals, bots, telegram, updates, kanban scheduling, the Kanban card's run-settings badges (`kanban-run-badges.test.js` pins the card's model/effort/engine chain against the one `startTask` actually resolves — the two live in different files and the card silently lies when they drift), the board-only `create_task` status (`task-backlog.test.js`), session expiry (`session-expiry.test.js` boots a real server TWICE against one `APP_DIR`, ages sessions in SQLite between the boots, and pins that expiry removes the worktree, branch and queued messages of a clean session while keeping one with uncommitted work, its Kanban cards, and an orphan tree younger than an hour), the external agents' model/effort catalogs (`agent-catalog.test.js` — #123: the Settings form and `POST /api/external-agents` both carry them, and the browser's comma parser is RUN against the server sanitiser so the two cannot drift), the Kanban group form (`kanban-group-button.test.js` RUNS the real `buildChainForm` in a `vm` context so a stray free variable in its template throws there instead of silently killing the modal — see #115 — and parses every inline `