Skip to content

fix(engine): the Subscription engine starts where claude was never opened - #126

Merged
Lexus2016 merged 1 commit into
mainfrom
fix/interactive-firstrun-screens
Oct 3, 2026
Merged

Lexus2016 merged 1 commit into
mainfrom
fix/interactive-firstrun-screens

Conversation

@Lexus2016

Copy link
Copy Markdown
Owner

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.

…opened

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[<dir>].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.
@Lexus2016
Lexus2016 merged commit e33ba0f into main Oct 3, 2026
2 checks passed
@Lexus2016
Lexus2016 deleted the fix/interactive-firstrun-screens branch October 3, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant