Skip to content

feat(g4): N1 input preconditions: prewrite, OAuth token, ready screen - #446

Merged
kappaseijin4claude merged 2 commits into
mainfrom
fix/issue-444-n1-input-preconditions
Sep 13, 2026
Merged

kappaseijin4claude merged 2 commits into
mainfrom
fix/issue-444-n1-input-preconditions

Conversation

@kappaseijin4claude

@kappaseijin4claude kappaseijin4claude commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

概要

N1 の各 case で prompt を送る前に満たす 3 つの前提を実装する。設計は docs/decisions/2026-09-13T151535_issue-444-n1-input-preconditions.md(PR #445)にある。どれか 1 つでも確かめられなければ、prompt を送らずに unknown とする。

Refs #444 #434 #407

変更

前提 実装
事前書込(§3) pilot-gate-isolation.py prewrite-claude-config。fresh と resume の各 launch の直前に毎回、$GATE_CLAUDE_CONFIG/.claude.json へ 3 つのキーを merge する。書くのは RUN_ROOT 配下の canonical なディレクトリだけで、symlink は書かない。他のキーは保ち、0600 で原子的に書き、読み戻して確かめる。N1/<mode>/claude-config-prewrite.json には、パス・設定したキー・読み戻しの結果だけを残す
token(§4) runner が見るのは環境変数 CLAUDE_CODE_OAUTH_TOKEN だけで、値は継承だけで渡す。ANTHROPIC_API_KEYANTHROPIC_AUTH_TOKEN は unset する。token が未設定・空・制御文字を含む場合は、fresh も resume も launcher を起動せず auth_token_absent とする。N1/auth.jsoncapture-environment には、有無と変数名だけを記録する
準備完了(§5) pilot-gate-isolation.py wait-n1-ready。binding の照合の後、ANSI 除去後の pty.raw を読む。? for shortcuts を観測し、かつ Select login methodQuick safety check を一度も観測していなければ準備完了とする。制限時間は 30 秒。確認済みの CLI 版(2.1.268)でなければ判定しない。キー入力は送らない
端末サイズ(§5.4) pilot-pty.py は子を起動する前に、PTY を 120x40 に設定する

unknown になった case は N1/<mode>/reason に理由を残し、N1 の result.json にもその理由を載せる。

README への影響

なし。gate harness は開発・受入用の内部ツールである。

設計書から判断した点

  • CLI 版の照合は、claude --version の出力の先頭トークン(例: 2.1.268 (Claude Code)2.1.268)と、runner の定数 N1_READY_VERIFIED_CLI_VERSIONS の完全一致で行う
  • RUN_ROOT の外を指す場合の reason は claude_config_outside_run_root とした(設計書は unknown とだけ定めている)
  • pilot-pty.py の端末サイズは、同じ spawn() を使う I1/F1〜F3 の NativePilot にも適用される。これらの既存試験はすべて通過している

試験(N1P-01〜15、合成値のみで実 token は使わない)

ID 置き場所 内容
01 / 13 / 15 runner bats 準備完了の画面を出す偽の launcher。prompt は 1 回だけ届き、端末は 40x120、競合する認証変数は子へ届かない
02 / 03 / 13 runner bats + isolation token が未設定・空・制御文字を含む場合、launcher の起動は 0 回、prompt も 0 回。auth.json には有無と変数名だけが残る
04 / 05 runner bats + isolation 阻害画面と準備完了の文字が同時に出ても login_method_screen / trust_dialog_screen になり、端末への入力は 0 バイト
06 isolation 何も表示されなければ ready_not_observed
07 runner bats + isolation 確認されていない CLI 版では ready_patterns_unverified_for_cli_version になり、prompt は 0 回
08 isolation 他のキーや他の project の設定は残る
09 isolation symlink、不正な JSON、object でない値は書き換えない
10 runner bats + isolation RUN_ROOT の外、RUN_ROOT 自体、symlink 経由、相対パスには書かず、launcher も起動しない
11 runner bats + isolation fresh の間に消された trust を、resume の前に書き直して読み戻しが通る
12 runner bats 起動した経路でも、起動しない経路でも、run tree に合成 token が 0 件。正の対照(token を書いたファイル 1 件)は検出される
14 isolation capture-environment の出力に、3 つの変数の合成値が 0 件

負の対照(原本は変更せず、コピーに変異を当てた。原本 3 ファイルの sha256 は前後で一致)

変異 判定
事前書込を呼ばない KILLED(N1P-10、N1P-11)
準備完了を待たない KILLED(N1P-01、04、05、07)
token を確認しない KILLED(N1P-02/03/12/13)
競合する認証変数を unset しない KILLED(N1P-01)
確認済みの版に 9.9.9 を加える KILLED(N1P-07)
準備完了の判定を阻害画面の判定より先にする KILLED(N1P-04/05、各 10/10。当初の fixture では約 5 割しか検出できず、reviewer の指摘で c9312aa にて修正)
ANSI を除去しない KILLED(エスケープで分断された準備完了の文字)
symlink を書く KILLED(N1P-09)
RUN_ROOT の確認を外す KILLED(N1P-10)
merge せずに上書きする KILLED(N1P-08、09)
制御文字を含む token を有効とする KILLED(N1P-03)
環境記録に値を出す KILLED(N1P-14)
timeout を準備完了とする KILLED(N1P-06)
端末サイズを設定しない KILLED(N1P-15)

検証(clean worktree)

試験 結果
tests/test_pilot_gate_runner.bats 52/52
tests/test_pilot_launcher.bats 33/33
fd guard / compat_posix / kill -0 規約(instance_id)/ close_fds 4 / 5 / 61 / 4
tests/test_pilot_gate_*.py 全 12 モジュール 全件通過(isolation 133)
tests/test_pilot_pty.py 18/18
check-enforced-assertions.sh 718(baseline のまま)

扱わない範囲

🤖 Generated with Claude Code

https://claude.ai/code/session_01QCtgi8wVJDRaMwXHZmkHqT

…#444)

Implements the design in docs/decisions/2026-09-13T151535_issue-444-n1-
input-preconditions.md. Without any of the three, the case is unknown and
no prompt is sent.

- Prewrite: before every fresh/resume launch, merge hasCompletedOnboarding,
  theme=dark and projects.<GATE_REPO>.hasTrustDialogAccepted into
  $GATE_CLAUDE_CONFIG/.claude.json. Only a canonical directory inside
  RUN_ROOT, never through a symlink, other keys kept, atomic 0600 write,
  read back; the record holds the path, the keys and the read-back only.
- Token: the runner only looks at CLAUDE_CODE_OAUTH_TOKEN in the
  environment and lets the pilot inherit it; ANTHROPIC_API_KEY and
  ANTHROPIC_AUTH_TOKEN are unset. Unset, empty or control characters:
  neither case starts a launcher (auth_token_absent). N1/auth.json and
  capture-environment record presence and names only.
- Ready: after the binding checks, pty.raw with ANSI escapes removed must
  show "? for shortcuts" and never "Select login method" or "Quick safety
  check", within 30 s, on a CLI version the verifier has observed
  (2.1.268). No key is sent to get past a screen.
- pilot-pty.py sets the child terminal to 120x40.

Tests N1P-01..15 with synthetic credential values only.

Refs #444 #434 #407

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gd6MbRAYGpkDzHAk1XZroZ
)

The stand-in launcher printed the blocking screen, slept one second, then
printed the ready text. A readiness poll inside that second saw only the
blocking screen, so a mutant that checks the ready text first was caught
only when the poll came later: the reviewer measured 5/10 and 6/10.

Both texts now arrive in one write. The priority mutant is killed 10/10
for each test and the unmutated code passes 10/10.

Refs #444

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gd6MbRAYGpkDzHAk1XZroZ
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.

2 participants