Skip to content

Claude Code: run on a Claude subscription (setup-token) + model/thinking passthroughs#18

Merged
armand0e merged 1 commit into
TeichAI:mainfrom
AletheiaResearch:claude-subscription-auth
Jul 10, 2026
Merged

Claude Code: run on a Claude subscription (setup-token) + model/thinking passthroughs#18
armand0e merged 1 commit into
TeichAI:mainfrom
AletheiaResearch:claude-subscription-auth

Conversation

@Quantumlyy

Copy link
Copy Markdown
Contributor

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-token on the host and export it as CLAUDE_CODE_OAUTH_TOKEN (TEICH_CLAUDE_OAUTH_TOKEN also works, or set agent.claude.oauth_token). There is no enable flag: subscription auth activates whenever the provider is claude-code, a token is resolvable, and no custom api.base_url is 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_KEY is withheld from subscription-auth containers — Claude Code silently prefers an API key over subscription credentials, which would bill the API instead.
  • An explicit api.base_url keeps the API/proxy path (incl. the OpenRouter proxy): an ambient env token is ignored there with a notice, and configuring agent.claude.oauth_token together with api.base_url is rejected.
  • Without a token, nothing changes — runs use the API-key path exactly as before.

Claude Code settings passthroughs

Config Mechanism
model.reasoning_effort --effort (shared field; Codex/Pi behavior unchanged)
agent.claude.fallback_model (string or list) --fallback-model, comma-joined
agent.claude.always_thinking alwaysThinkingEnabled in the seeded ~/.claude/settings.json, merged with the Langfuse hooks
agent.claude.max_thinking_tokens MAX_THINKING_TOKENS container env (0 stays meaningful: disables thinking)

Docs & tests

config.example.yaml + the teich init template (kept byte-identical, enforced by the existing test), new docs/generation.md sections, 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.

…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.
@Quantumlyy

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/teich/config.py
Comment on lines +465 to +466
if isinstance(token, str) and token.strip():
return token.strip()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread src/teich/runner.py
Comment on lines +3428 to +3432
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])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@armand0e armand0e self-assigned this Jul 10, 2026
@armand0e
armand0e merged commit e0a546e into TeichAI:main Jul 10, 2026
3 checks passed
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