fix(login): submit via paste+separate Enter and navigate onboarding

Reproduced the failure against a real claude 2.1 in tmux. Two root causes,
both now fixed (the URL was never wrong — claude genuinely emits
`claude.com/cai/oauth/authorize`, so extraction was fine):

1. Submit race (the actual failure). `send_keys` sent the code and Enter
   together; for a long real code the Enter is processed before Ink commits the
   paste, so nothing submits — the session sits at "Paste code here > ****…",
   exactly what the activity log showed. Fix: deliver the code as a bracketed
   paste (tmux set-buffer/paste-buffer, new tmux.send_text), let it settle, then
   send Enter separately (tmux.send_enter). Verified end-to-end: the separate
   Enter submits and claude proceeds to the exchange.

2. Fragile onboarding. A fresh claude shows a theme picker, then the
   login-method menu, before any URL — the old blind /login+Enter+Enter only
   reached the menu by luck. Fix: start() now reads the pane each pass and reacts
   — accept theme/trust/continue prompts, pick the default subscription option on
   the login-method menu, and send /login once only when already onboarded at the
   REPL.

Also: confirm login by watching ~/.claude.json (where claude stores the account
on Linux) plus a success-text fallback, and fail fast on an "OAuth error /
Press Enter to retry" screen instead of waiting out the poll. Tests updated to
the real TUI screen text. Suite green (200).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKVyBmKvWDVgrFC9WER2f2
This commit is contained in:
Claude
2026-07-15 15:34:43 +00:00
parent f51a49fb98
commit 1fe260ebe4
4 changed files with 211 additions and 102 deletions
+15 -1
View File
@@ -74,7 +74,13 @@ def auth(env):
@pytest.fixture
def fake_tmux(monkeypatch):
"""Record tmux calls instead of spawning; report sessions as live by default."""
calls: dict[str, list] = {"new_session": [], "kill_session": [], "send_keys": []}
calls: dict[str, list] = {
"new_session": [],
"kill_session": [],
"send_keys": [],
"send_text": [],
"send_enter": [],
}
live: set[str] = set()
from handler.control import tmux
@@ -96,6 +102,12 @@ def fake_tmux(monkeypatch):
def send_keys(name, keys):
calls["send_keys"].append({"name": name, "keys": keys})
def send_text(name, text):
calls["send_text"].append({"name": name, "text": text})
def send_enter(name):
calls["send_enter"].append({"name": name})
def list_sessions():
return list(live)
@@ -103,6 +115,8 @@ def fake_tmux(monkeypatch):
monkeypatch.setattr(tmux, "has_session", has_session)
monkeypatch.setattr(tmux, "kill_session", kill_session)
monkeypatch.setattr(tmux, "send_keys", send_keys)
monkeypatch.setattr(tmux, "send_text", send_text)
monkeypatch.setattr(tmux, "send_enter", send_enter)
monkeypatch.setattr(tmux, "list_sessions", list_sessions)
return {"calls": calls, "live": live}