Claude Code: run on a Claude subscription (setup-token) + model/thinking passthroughs#18
Conversation
…ing passthroughs Adds Claude Code parity with the Codex subscription-auth work: - Subscription auth: when the provider is claude-code, a long-lived `claude setup-token` OAuth token is resolvable (agent.claude.oauth_token, or the TEICH_CLAUDE_OAUTH_TOKEN / CLAUDE_CODE_OAUTH_TOKEN env vars), and no api.base_url is configured, Teich passes the token into each container as CLAUDE_CODE_OAUTH_TOKEN and withholds ANTHROPIC_API_KEY (an API key silently wins over subscription credentials inside Claude Code and would bill the API). There is no enable flag and, unlike Codex, no broker: the token does not rotate, so any max_concurrency is safe and the host login is never disturbed. Usage bills the plan's rate-limit windows, not API credits. - Guardrails: an explicit api.base_url keeps the API/proxy path (an ambient env token is ignored there, with a CLI notice), while a configured oauth_token combined with api.base_url is rejected. The CLI notice names the resolving token source via get_claude_oauth_token_source(), colocated with the getter so the report can't drift from the resolution order. - Claude Code passthroughs under agent.claude: fallback_model (string or list -> --fallback-model, comma-joined), always_thinking (alwaysThinkingEnabled in the seeded ~/.claude/settings.json, merged with the Langfuse hooks), max_thinking_tokens (MAX_THINKING_TOKENS container env; 0 stays meaningful and disables thinking). The shared model.reasoning_effort now forwards to Claude Code as --effort. - Docs: config.example.yaml + the teich init template (kept byte-identical), docs/generation.md subscription-auth and settings sections, README.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4ede86e72b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if isinstance(token, str) and token.strip(): | ||
| return token.strip() |
There was a problem hiding this comment.
Normalize configured Claude token before enabling host auth
agent.claude.oauth_token is returned verbatim here, unlike API keys and env token aliases that go through _normalize_api_key. If a config uses a placeholder such as oauth_token: none/dummy while an API key is configured, claude_host_auth_active() treats that placeholder as a real subscription token and _api_env_items() then withholds ANTHROPIC_API_KEY, so Claude runs fail authentication; the same raw value also trips the base_url conflict validator. Please normalize the configured token before treating it as present.
Useful? React with 👍 / 👎.
| if self.config.model.reasoning_effort: | ||
| claude_command.extend(["--effort", self.config.model.reasoning_effort]) | ||
| fallback_model = self.config.get_claude_fallback_model() | ||
| if fallback_model: | ||
| claude_command.extend(["--fallback-model", fallback_model]) |
There was a problem hiding this comment.
Mirror Claude flags in Studio terminal command
These passthroughs are only appended by _build_shell_command, but Studio terminal sessions build a separate Claude command in src/teich/studio/interactive.py::_native_cli_command and do not call this method. In that context, a user starting Claude Code from Studio with model.reasoning_effort or agent.claude.fallback_model gets a plain claude --model ..., so the newly documented settings are silently ignored; mirror the same flags there or scope the feature to batch generation.
Useful? React with 👍 / 👎.
Adds Claude Code parity with the Codex subscription-auth work (#14): run Claude Code on a Claude Pro/Max subscription instead of an API key, plus the Claude-specific settings passthroughs.
Subscription auth (Claude OAuth token)
Create a long-lived token with
claude setup-tokenon the host and export it asCLAUDE_CODE_OAUTH_TOKEN(TEICH_CLAUDE_OAUTH_TOKENalso works, or setagent.claude.oauth_token). There is no enable flag: subscription auth activates whenever the provider isclaude-code, a token is resolvable, and no customapi.base_urlis configured — the CLI prints a notice naming the resolving source when active.Unlike Codex there is no broker, by design: the setup-token credential does not rotate, so containers can share it at any
max_concurrency, the interactive host login is never disturbed, and usage bills the plan's rate-limit windows rather than pay-per-token API credits.Guardrails:
ANTHROPIC_API_KEYis withheld from subscription-auth containers — Claude Code silently prefers an API key over subscription credentials, which would bill the API instead.api.base_urlkeeps the API/proxy path (incl. the OpenRouter proxy): an ambient env token is ignored there with a notice, and configuringagent.claude.oauth_tokentogether withapi.base_urlis rejected.Claude Code settings passthroughs
model.reasoning_effort--effort(shared field; Codex/Pi behavior unchanged)agent.claude.fallback_model(string or list)--fallback-model, comma-joinedagent.claude.always_thinkingalwaysThinkingEnabledin the seeded~/.claude/settings.json, merged with the Langfuse hooksagent.claude.max_thinking_tokensMAX_THINKING_TOKENScontainer env (0stays meaningful: disables thinking)Docs & tests
config.example.yaml+ theteich inittemplate (kept byte-identical, enforced by the existing test), newdocs/generation.mdsections, README. New unit tests cover token resolution/precedence and validation, runner command/env/settings.json assembly, and the CLI notices. Full suite: 560 passed.Note: this is independent of #17 (bench); once both land, a tiny follow-up can export the token in the bench backends' task-container env as well.