omp: Use the AI Subscriptions You Already Pay For, From Any App

One local address for every AI plan you already pay for: how omp’s auth broker and gateway work, and how to run Claude Code through them.

If you’re like many people, you pay for more than one AI plan: ChatGPT Plus for work, Claude Pro, Max or Ultra for writing, GitHub Copilot or Cursor through your job, maybe a Z.AI GLM Coding Plan on the side. Each one works only inside its vendor’s own app.

Then you find a tool you’d like to try: an editor plugin, a notes app with an "AI" button, a script a friend shared, a different coding agent. It asks for an API key. That’s a separate product, billed per token, on top of the subscription you already pay for.

There’s a way around that. omp is an open-source coding agent, and it ships a small server that most people never notice: the auth gateway. You log in to your subscriptions once, and it gives every app on your machine one address to talk to. It speaks the same "API language" as OpenAI and Anthropic, which most AI tools already understand.

What that gets you:

  • One address for all your plans. Claude, ChatGPT/Codex, Copilot, Cursor, OpenCode, Z.AI and DeepSeek all sit behind http://127.0.0.1:4000, and the model name in each request decides which plan answers.
  • Tools that already work with it. Anything with a "custom base URL" or "OpenAI-compatible" setting connects: SDKs, editor plugins, curl, Claude Code, Codex CLI.
  • Your real logins stay put. Apps get one gateway token that you can rotate. Your passwords and OAuth tokens never leave omp’s vault.
  • Mix and match. Run Claude Code on a GPT model, or point an OpenAI-SDK script at Claude.

Here is the whole idea, start to finish:

omp auth-broker login openai-codex                     # 1. log in once, in your browser
omp auth-broker serve                                  # 2. the vault (leave it running)
OMP_AUTH_BROKER_URL=http://127.0.0.1:8765 \
  omp auth-gateway serve                               # 3. the gateway, in a second terminal
# 4. in any app: base URL http://127.0.0.1:4000/v1, API key = the output of `omp auth-gateway token`

The rest of this article covers the CLI, the setup in five phases, and then a worked example: setting up Claude Code to run through the gateway. Before you connect a consumer plan, read the note on subscription terms at the end.

Written against omp 18.18.1 (November 2026). The gateway is moving fast; check omp auth-gateway --help on your version.

How the pieces fit

  any client (curl, SDKs, Claude Code, Codex CLI, editor plugins…)
        │  Authorization: Bearer <gateway token>
        ▼
  omp auth-gateway serve      127.0.0.1:4000   /v1/messages, /v1/chat/completions, /v1/responses, …
        │  broker token (the gateway only)
        ▼
  omp auth-broker serve       127.0.0.1:8765   SQLite vault, background OAuth refresher
        │
        ▼
  api.anthropic.com · chatgpt.com (Codex) · api.githubcopilot.com · opencode.ai · …
  • The broker is the only process that holds OAuth refresh tokens. It refreshes them in the background (it checks every 60 s and renews anything due to expire within 5 minutes). It is not the endpoint clients call.
  • The gateway is a broker client. It receives a redacted credential snapshot, in which every refresh token is replaced by a sentinel, and asks the broker to refresh a credential when its access token expires. Each request goes through omp’s own provider code, so the request shaping, provider quirks and refresh-on-401 are the same ones omp’s agent uses. There is no raw passthrough.
  • Neither service runs omp’s agent loop. The client keeps its own planning, tools and conversation; omp supplies only upstream auth and format translation.

The CLI

Two command families, each with a handful of verbs.

omp auth-broker: the vault

omp auth-broker serve   [--bind=host:port] [--trust-proxy-headers]   # run the vault (default 127.0.0.1:8765)
omp auth-broker token   [--regenerate] [--json]                      # print or rotate the broker token
omp auth-broker list                                                 # every provider omp can log in to
omp auth-broker login   [<provider>] [--via=user@host] [--dry-run]   # log in (no provider: pick from a menu)
omp auth-broker logout  [<provider>]
omp auth-broker import  <file|dir> [--provider=<id>] [--dry-run]     # bring in exported credentials
omp auth-broker migrate --from-local [--include-oauth] [--include-env] [--dry-run]
omp auth-broker status  [--json]

omp auth-broker list is worth running once. On 18.18.1 it lists about sixty providers. The big ones:

Provider id What it is
anthropic Claude Pro, Max and Ultra plans
openai-codex ChatGPT Plus/Pro (Codex subscription); openai-codex-device for headless logins
github-copilot GitHub Copilot
cursor Cursor (browser login; serves Claude, GPT and more)
zai-coding-plan, zai Z.AI GLM Coding Plan: browser sign-in, or an API key (ZAI_API_KEY)
opencode-go, opencode-zen OpenCode plans (API key, OPENCODE_API_KEY)
deepseek DeepSeek (API key, DEEPSEEK_API_KEY)
openrouter, kimi-code, google-gemini-cli, xai-oauth, … the long tail

omp auth-gateway: the HTTP front end

omp auth-gateway serve  [--bind=host:port] [--no-auth] [--clients=<file>] [--trust-proxy-headers]
omp auth-gateway token  [--regenerate] [--json]     # the token clients send (created on first run)
omp auth-gateway status [--json]                    # local token file + broker reachability
omp auth-gateway check  [--strict] [--json]         # probe every credential; --strict spends a little quota
omp auth-gateway stdio                              # same routes as JSON lines, for one parent process

serve needs to know where the broker is, through OMP_AUTH_BROKER_URL (or auth.broker.url in config.yml). The default bind is 127.0.0.1:4000.

Setup, in phases

Do every phase as the same OS user, with the same omp config. The broker reads the credential store that your logins wrote, and a different user or profile means a different, empty store.

Phase 1: log in to your subscriptions

omp auth-broker login anthropic          # Claude Pro/Max/Ultra
omp auth-broker login openai-codex       # ChatGPT Plus/Pro (Codex)
omp auth-broker login github-copilot     # Copilot
omp auth-broker login cursor             # Cursor
omp auth-broker login zai-coding-plan    # Z.AI GLM Coding Plan

Each one prints a URL to open in a browser, and some ask you to paste a code back. API-key providers such as OpenCode and DeepSeek go through omp’s interactive login instead: run omp, type /login, and pick the provider. For OpenCode it points you to https://opencode.ai/auth and stores the key as OPENCODE_API_KEY; DeepSeek works the same way with DEEPSEEK_API_KEY.

Check each login before going further:

omp models openai-codex
omp -p --no-session --model openai-codex/gpt-5.6-sol "Reply with exactly: PONG"

Phase 2: start the broker

omp auth-broker serve

On first start it writes its token to <config-dir>/auth-broker.token (mode 0600). Leave it running in its own terminal, screen or tmux window, or service.

Phase 3: start the gateway

In a second terminal:

OMP_AUTH_BROKER_URL=http://127.0.0.1:8765 omp auth-gateway serve

On the same host and user, the gateway finds the broker token file by itself. A gateway on another host or user needs the broker token passed explicitly.

The gateway rebuilds its model catalog every 15 minutes and checks for credential changes every 10 seconds. Still, restart the gateway after adding or removing a login. It is the quickest way to be sure /v1/models reflects the change.

Phase 4: choose how clients authenticate

By default every route except /healthz requires the gateway token:

export OMP_GATEWAY_TOKEN="$(omp auth-gateway token)"

There are two alternatives.

  • --no-auth turns the bearer check off. Any process that can reach the listener can spend your subscriptions, so use it only on loopback, and keep --bind explicit, because the flag doesn’t force a loopback bind:

    OMP_AUTH_BROKER_URL=http://127.0.0.1:8765 \
      omp auth-gateway serve --bind=127.0.0.1:4000 --no-auth
    
  • --clients=<file> admits extra, scoped tokens. Each one is limited to a list of models and accounts. The file stores only SHA-256 digests and is re-read every 10 seconds while the gateway runs:

    {
      "ci": {
        "tokenSha256": "<printf %s \"$TOKEN\" | shasum -a 256>",
        "models": ["openai-codex/*", "anthropic/claude-haiku-5-5"]
      }
    }
    

    A model outside the list answers 404 Unknown model, and that client’s /v1/models shows only what it was granted.

Phase 5: smoke test

G=http://127.0.0.1:4000
AUTH="Authorization: Bearer $OMP_GATEWAY_TOKEN"

curl -s $G/healthz                                   # {"ok":true,"version":"18.18.1"}
curl -s $G/v1/models -H "$AUTH" | jq -r '.data[].id'

/v1/models is the authoritative list of what you can call: the catalog, filtered to the providers you hold credentials for. Each row carries more than an ID:

{
  "id": "anthropic/claude-fable-5",
  "owned_by": "anthropic",
  "api": "anthropic-messages",
  "display_name": "Claude Fable 5",
  "input_modalities": ["text", "image"],
  "context_length": 1000000,
  "max_output_tokens": 128000
}

Then one request per chat format:

# Anthropic Messages
curl -s $G/v1/messages -H "$AUTH" -H 'anthropic-version: 2023-06-01' -H 'Content-Type: application/json' \
  -d '{"model":"anthropic/claude-haiku-5-5","max_tokens":100,
       "messages":[{"role":"user","content":"What is 17*3?"}]}'

# OpenAI Chat Completions
curl -s $G/v1/chat/completions -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"model":"openai-codex/gpt-5.6-sol","messages":[{"role":"user","content":"What is 17*3?"}]}'

# OpenAI Responses
curl -s $G/v1/responses -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"model":"openai-codex/gpt-6.1-sol","input":"What is 17*3?"}'

Add "stream": true to get SSE back in the same wire format.

Routing: the path picks the format, model picks the backend

Path Wire format
POST /v1/messages Anthropic Messages
POST /v1/chat/completions OpenAI Chat Completions
POST /v1/responses OpenAI Responses
POST /v1/pi/stream omp’s native stream format

The gateway never guesses the format from the body. The backend comes only from the model field, as provider/model-id, so the two are independent. Here is an Anthropic-format request answered by a Codex subscription, a real reply:

{
  "type": "message",
  "role": "assistant",
  "model": "openai-codex/gpt-5.6-sol",
  "content": [{ "type": "text", "text": "17 × 3 is 51." }],
  "stop_reason": "end_turn",
  "usage": { "input_tokens": 18, "output_tokens": 12, "cache_read_input_tokens": 0, "cache_creation_input_tokens": 0 }
}

Bare IDs work too. gpt-5.6-sol with no prefix goes to the highest-ranked provider that serves it. Native accounts (openai-codex, anthropic, openai) rank ahead of aggregators, and modelProviderOrder in config.yml overrides the ranking. Use the prefixed form when it matters which account pays.

Beyond chat

The gateway has more routes than chat. They are real, but omp doesn’t generate audio or images itself. Each route forwards to a provider that offers that kind of model, so it only works once the broker holds a login for such a provider:

Route What Who serves it (omp 18.18.1)
POST /v1/images/generations, /v1/images/edits image generation and edits (OpenAI Images wire) Codex subscription (gpt-image-2), OpenAI API key, OpenRouter
POST /v1/audio/speech text-to-speech, returns raw audio xAI (grok-tts: API key, or the SuperGrok / X Premium+ login), OpenRouter, DeepInfra
POST /v1/audio/transcriptions speech-to-text (multipart file, 25 MiB cap) OpenAI API key, OpenRouter
POST /v1/embeddings embeddings (OpenAI wire) OpenAI API key, OpenRouter
POST /v1/rerank rerank (OpenRouter wire) OpenRouter
POST /v1/videos, GET /v1/videos/:id[/content] video jobs: submit, poll, download OpenRouter
GET /v1/usage your subscription usage windows, across every account every login
GET /v1/credentials/check per-credential health, handy when something starts returning 401 every login

Of the big subscriptions, only Codex adds anything here: image generation. Claude, Copilot, Cursor, OpenCode, Z.AI and DeepSeek are chat-only through the gateway. An OpenRouter key turns on almost every route, and the "OpenAI API key" rows mean a direct OPENAI_API_KEY, not the Codex login.

/v1/models marks non-chat rows with a kind field (image, tts, stt, embedding, rerank, video, …), so check there what your logins actually unlock. With no provider for a modality, its route has no model to call and the request is refused. Sending a chat route a non-chat model fails with a 400 that names the route to use instead.

omp auth-gateway stdio serves all of these to a single parent process over stdin/stdout, with no port and no token. It suits editor plugins and scripts:

echo '{"id":1,"path":"/v1/chat/completions","body":{"model":"@smol","messages":[{"role":"user","content":"hi"}]}}' \
  | omp auth-gateway stdio

Unlike serve, stdio uses your normal omp setup, including models.yml, extension providers and role selectors like @smol.

Worked example: Claude Code on the gateway

Claude Code speaks Anthropic Messages, and it lets you replace its endpoint and credential through environment variables.

1. The essentials

export ANTHROPIC_BASE_URL=http://127.0.0.1:4000          # no /v1: Claude Code appends it
export ANTHROPIC_AUTH_TOKEN="$OMP_GATEWAY_TOKEN"          # sent as Authorization: Bearer
unset ANTHROPIC_API_KEY
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1       # let /model list the gateway's models
claude

Use ANTHROPIC_AUTH_TOKEN, not ANTHROPIC_API_KEY. The gateway accepts only Authorization: Bearer, and ANTHROPIC_API_KEY sends x-api-key, which gets a 401. With --no-auth the token is ignored, but set it to a placeholder such as unused if a client insists on one.

Inside Claude Code, run /status to confirm the base URL, and /model to pick from what the gateway serves. A gateway ID that’s missing from the picker can be added with ANTHROPIC_CUSTOM_MODEL_OPTION=anthropic/claude-sonnet-5-5.

2. Pin every model slot

Claude Code doesn’t use one model for everything. Background tasks, the "fast" model and subagents each pick their own. If you don’t set them, they ask the gateway for model names it may not serve. Pin them all:

M=anthropic/claude-sonnet-5-5
export ANTHROPIC_MODEL=$M
export ANTHROPIC_DEFAULT_OPUS_MODEL=$M
export ANTHROPIC_DEFAULT_SONNET_MODEL=$M
export ANTHROPIC_DEFAULT_HAIKU_MODEL=anthropic/claude-haiku-5-5   # cheaper model for background work
export ANTHROPIC_DEFAULT_FABLE_MODEL=$M
export CLAUDE_CODE_SUBAGENT_MODEL=$M

3. Or: Claude Code’s harness, a GPT brain

Because the backend is just the model field, you can point the same Claude Code at your Codex subscription:

M=gpt-5.6-sol            # bare ID; resolves to openai-codex/gpt-5.6-sol
export ANTHROPIC_MODEL=$M ANTHROPIC_DEFAULT_OPUS_MODEL=$M ANTHROPIC_DEFAULT_SONNET_MODEL=$M \
       ANTHROPIC_DEFAULT_HAIKU_MODEL=$M ANTHROPIC_DEFAULT_FABLE_MODEL=$M CLAUDE_CODE_SUBAGENT_MODEL=$M
export CLAUDE_CODE_EFFORT_LEVEL=high
claude

This works day to day. The tool calls translate, streaming works and the agent loop runs. Anthropic doesn’t support Claude Code on non-Claude models, though, and features that exist on only one side (prompt caching details, some thinking controls, server-side tools) don’t translate into each other.

4. Wrap it

Put the block in a small launcher script (claude-omp) and keep Claude Code’s state separate from your normal setup with CLAUDE_CONFIG_DIR=~/.claude-omp. Then your regular claude stays on its own login, and claude-omp runs on the gateway.

Other clients, briefly

  • OpenAI SDKs and tools use base_url="http://127.0.0.1:4000/v1" with the gateway token as the API key.
  • Codex CLI needs a custom provider with base URL http://127.0.0.1:4000/v1, the responses wire API, the gateway token, and an exact model ID from /v1/models. Its own ChatGPT login has nothing to do with omp’s Codex login.
  • Anything Anthropic-shaped uses the Claude Code variables above.

Two tokens, different power

Token File Grants Give it to
Gateway token <config-dir>/auth-gateway.token inference through the gateway, never the upstream credentials your clients
Broker token <config-dir>/auth-broker.token credential snapshots: real OAuth access tokens and stored API keys the gateway, and nothing else

"Clients need no API keys" means clients never receive an upstream credential. It doesn’t mean the gateway is open. The gateway token is a real secret, and anyone holding it can spend your quota. Transport security is up to you: the gateway speaks plain HTTP, so if it leaves loopback, put TLS or a private network in front of it. A request that carries the gateway token in its URL, or in a logged or forwarded header, is rejected before any credential lookup, which keeps the token out of logs.

Limits and gotchas

  • models.yml is ignored by serve. Your custom providers, local base URLs and overrides are deliberately left out, so local config can’t redirect broker-backed traffic. (stdio honours them.)
  • No aliases and no default model. Clients must send a model the gateway serves.
  • No web-search route. Clients’ built-in web-search tools aren’t backed by omp’s search engines.
  • A catalog entry is not account access. /v1/models lists what your credentials can reach in principle, and your plan decides the rest.
  • Envelope translation is not feature parity. A cross-format request carries text, images, tools and streaming, not every vendor-specific knob.
  • Same user, same config. Most "it lists no models" problems come from a broker started under a different user or profile from the one that did the logins.

A note on subscription terms

The gateway makes it easy to use a consumer subscription from any client. Whether your subscription allows that is a separate question, and the answer differs by vendor and changes over time. Claude Pro/Max/Ultra in particular is meant for Anthropic’s own apps and Claude Code, and re-exposing it to other clients can get an account flagged. omp offering a login doesn’t make every use of it allowed. Read the current terms for each plan you wire in. Where it matters, use the provider’s API-key option, which the gateway routes the same way.


Originally published in TheBigPromptLibrary on GitHub (view source).
⭐ Star the library on GitHub for more system prompts and research.

When “Just Do It” Isn’t Enough: Invoking Claude Code Tools by Name

The cases where naming the exact tool beats describing the outcome.

Claude Code exposes a few dozen tools, and most of the time you should never name one. You say what you want ("find every place we validate emails", "build it and run it") and the model routes the request to the right tool. That default is good, and you should lean on it.

But routing is the model’s guess at your intent, and sometimes the guess isn’t what you wanted. It answers when you wanted to be asked. It presses on when you wanted a checkpoint. It skips the task list you wanted kept. It improvises three shell commands when you wanted one specific search. In those moments the fix is the same: stop describing the outcome and name the tool. Saying "use the AskUserQuestion tool" or "use TaskCreate" or "use Monitor" turns a guess into a guarantee.

This article is about those moments, the legitimate cases where naming the tool gives you deterministic control, followed by a full, typed reference so you know exactly what to say and with which arguments.

Two ways to invoke, and when to switch

The model is a good dispatcher. Tell it what you want and it walks to the toolbox, picks a tool, and uses it. Ninety percent of the time that is exactly right, and you should let it drive. But a dispatcher is guessing at your intent, and every so often you want to reach past it and grab a specific tool yourself. That reach is the whole skill this article teaches: knowing the names well enough to ask for one on purpose.

  • Outcome phrasing (the default): "Find where auth is handled." The model picks the tool. Best for almost everything.
  • By-name invocation (this article): "Use the Grep tool with pattern=’validateAuth’, output_mode=’content’." You pick the tool and, optionally, its exact arguments. Best when the specific behavior matters more than the outcome.

Switch to by-name when any of these is true: you want a specific mechanism (this tool, not a substitute), you want a behavior the model wouldn’t trigger on its own, or the tool is keyword-gated and has no other way in.

When naming the tool wins

Make it stop and ask you: AskUserQuestion

Here is a fun one to try right now. Prompt the model with:

"Use the AskUserQuestion tool and present me three color choices."

It pops a little structured menu, red / green / blue, and waits for you to pick. Nothing about choosing a color needs a tool, which is exactly why it makes such a clean demo: you aren’t asking for an outcome, you are commandeering a specific piece of UI on demand. The model is smart enough to reach for this tool on its own, but here you are the one deciding it should.

Now swap the toy for something real:

"Use the AskUserQuestion tool to give me options before you choose the auth library."

Same move, real stakes. You convert "the model decided" into "the model asked." This is the single most useful by-name habit to build.

Force a task to exist: TaskCreate / TaskUpdate

The todo system auto-engages on big, multi-step work, but for a two- or three-step job it may not bother. When you want a durable, visible checklist regardless of the model’s judgment:

"Use TaskCreate to add a task: subject=’Migrate auth to OAuth’. Then keep it updated as you go." "Use TaskUpdate taskId=2, addBlockedBy=[‘1’] so task 2 waits for task 1."

Naming TaskCreate makes the task exist on purpose; naming TaskUpdate lets you drive status and dependencies by hand instead of hoping they’re tracked.

Start something recurring or watching, on purpose: CronCreate, Monitor, /loop

"Keep an eye on the build" is ambiguous; the mechanism you actually want isn’t. Name it, and you choose polling vs. streaming vs. self-pacing:

"Use CronCreate cron=’*/15 * * * *’ to re-run the tests every 15 minutes." "Use Monitor command=’tail -f dev.log | grep –line-buffered ERROR’ to tell me every time an error appears." "/loop 5m /babysit-prs" to run a prompt on an interval.

Behaviors that only exist by name: the keyword-gated tools

Some tools cannot be summoned any other way. Here the name or keyword is the API:

  • worktree unlocks EnterWorktree: "do this in a worktree."
  • ultracode (or "use a workflow") unlocks Workflow: deliberate multi-agent orchestration.
  • /loop with no interval unlocks ScheduleWakeup: model-paced loops.
  • an active code review unlocks ReportFindings: typed findings to the UI.

No amount of outcome phrasing unlocks these; the deliberate keyword is the point. They can spawn many agents or relocate your edits, so they wait for explicit consent.

Pin the exact operation: Grep, Glob, Edit, Agent

When you want one specific mechanism and no substitution:

"Use Grep with pattern=’TODO’, output_mode=’content’, -n=true" (not a subagent). "Use an Explore agent to search, don’t grep inline." "Use Edit on config.ts with replace_all=true."

Naming removes the routing guess when the how matters as much as the what.

Run a specific, known procedure: Skill

Skills are packaged multi-step procedures. Naming one runs that procedure rather than hoping the model reconstructs the same steps:

"Use the deep-research skill on X", or type /deep-research <question>.

Choose the output channel: Artifact, PushNotification, WebFetch

When you want a specific destination for the result:

"Make an Artifact I can share." / "Use PushNotification to ping me when the build finishes." / "Use WebFetch url=… prompt=’what changed in 2.1?’."

How to phrase a by-name call

Two levels of precision:

  • Name only: "Use TaskCreate to track this." The model fills in the arguments.
  • Name plus arguments: "Use TaskCreate with subject=’…’, description=’…’, activeForm=’…’." You pin the exact call.

The rest of this article is the reference that makes the second form possible: every tool, its parameters, types, and defaults.


The catalog: always-loaded vs deferred

The reference is the full toolset of a Claude Code 2.1.223 session (Windows, August 2026): 32 tools, 13 always-loaded and 19 deferred. Always-loaded tools are callable immediately; deferred tools are announced by name and their schemas load on demand (the model pulls one in with a ToolSearch call before first use). Parameters are marked * when required, with types and defaults noted.

File, search & shell

  • Read: read a file (text, image, PDF, notebook).
    • file_path* string (absolute) · offset int · limit int · pages string (PDF range)
  • Write: create or overwrite a file you’ve Read.
    • file_path* string · content* string
  • Edit: exact string replacement.
    • file_path* string · old_string* string · new_string* string · replace_all bool=false
  • Glob: filename pattern match, sorted by mtime.
    • pattern* string · path string
  • Grep: ripgrep content search.
    • pattern* string · path · glob · type · output_mode content|files_with_matches|count · -i/-n/-o bool · -A/-B/-C int · multiline bool · head_limit int=250 · offset int
  • Bash: Git Bash / POSIX shell.
    • command* string · description · timeout ms=120000 (max 600000) · run_in_background bool · dangerouslyDisableSandbox bool
  • PowerShell: pwsh (primary shell on Windows; absent on macOS/Linux).
    • command* string · description · timeout ms (max 600000) · run_in_background bool · dangerouslyDisableSandbox bool
  • NotebookEdit: replace/insert/delete one Jupyter cell.
    • notebook_path* string · new_source* string · cell_id string · cell_type code|markdown · edit_mode replace|insert|delete (default replace)

Delegation

  • Agent: spawn a subagent.
    • description* string (3 to 5 words) · prompt* string · subagent_type string · model sonnet|opus|haiku|fable · isolation worktree|remote · run_in_background bool=true
    • subagent_type values: claude, general-purpose, Explore, Plan, statusline-setup.
  • SendMessage: the only way agents talk to each other; resume a spawned agent with its context, or post to main.
    • to* string (teammate name, or "main") · message* string · summary string (<=200, required when message is a string)
  • Workflow (gated: ultracode): deterministic JS orchestration over many subagents.
    • script string · scriptPath string · name string · args any · resumeFromRunId (wf_…)

Task list & background jobs

  • TaskCreate: new task (starts pending).
    • subject* string · description* string · activeForm string · metadata object
  • TaskUpdate: modify a task.
    • taskId* string · status pending|in_progress|completed|deleted · addBlockedBy[] · addBlocks[] · owner · subject · description · activeForm · metadata
  • TaskList: list all tasks. (no params)
  • TaskGet: one task’s full detail.
    • taskId* string
  • TaskStop: kill a background task or named agent.
    • task_id string (also name@team or a bare agent name) · shell_id (deprecated)
  • TaskOutput (deprecated): read a background job’s output.
    • task_id* string · block bool=true · timeout ms=30000 (max 600000)

Scheduling & watching

  • CronCreate: schedule a prompt on 5-field local-time cron; session-only, recurring auto-expires after 7 days.
    • cron* string · prompt* string · recurring bool=true · durable (no effect)
  • CronList: list this session’s cron jobs. (no params)
  • CronDelete: cancel one. · id* string
  • Monitor: stream events from a command’s stdout or a WebSocket; one notification per line.
    • description* string · timeout_ms* number=300000 (max 3600000) · persistent* bool=false · command string or ws {url* string, protocols[]} (mutually exclusive)
  • RemoteTrigger: claude.ai cloud routines (backs /schedule).
    • action* list|get|create|update|run · trigger_id string · body object (required for create/update)
  • ScheduleWakeup (gated: /loop without interval): self-paced loop scheduling.
    • delaySeconds number (60 to 3600) · prompt string · reason string · stop bool

Web & output

  • WebFetch: fetch a URL, convert to markdown, answer a prompt (15-min cache, HTTP upgraded to HTTPS, no auth).
    • url* uri · prompt* string
  • WebSearch: web search (US-only).
    • query* string (min 2) · allowed_domains[] · blocked_domains[]
  • PushNotification: desktop/phone ping.
    • message* string (<200 chars) · status* const "proactive"
  • AskUserQuestion: structured multiple-choice back to you (interactive only).
    • questions[]* where each has: question string, header string (<=12), options[] (2 to 4: label, description, preview), multiSelect bool

Planning, isolation & design

  • EnterPlanMode / ExitPlanMode: read-only planning and approval (interactive; also shift+tab). (no params)
  • EnterWorktree (gated: worktree): isolated git worktree.
    • name string (<=64) or path string, mutually exclusive
  • ExitWorktree: leave the worktree session.
    • action* keep|remove · discard_changes bool (true to remove a dirty worktree)
  • DesignSync: read/write claude.ai design-system projects; ordering is read, then finalize_plan, then write.
    • method* list_projects|get_project|list_files|get_file|finalize_plan|write_files|delete_files|register_assets|unregister_assets|create_project|report_validate · plus projectId, planId, path/paths[], writes[]/deletes[], localDir, files[], assets[], name

Specialized & internal

  • Skill: run a packaged skill / slash command.
    • skill* string (exact name, no leading slash) · args string
  • ReportFindings (gated: code-review): typed findings to the UI.
    • findings[]* where each has: file, summary, failure_scenario*, line, category, short_summary, verdict, outcome · level low|medium|high|xhigh|max
  • Artifact: publish an HTML/Markdown file as a private claude.ai page (interactive).
    • file_path string · favicon emoji (required to publish) · title · description · url (update) · action publish|list
  • ToolSearch: load deferred tool schemas on demand (internal; never prompt for it).
    • query* string (select:A,B, keywords, or +name term) · max_results* number=5

Compact reference

Loaded = always-on; Deferred = schema loads on demand. * = required.

Tool Loaded Purpose Key arguments
Read always read file / image / PDF / notebook file_path*, offset, limit, pages
Write always create / overwrite a Read file file_path, content
Edit always exact string replacement file_path, old_string, new_string*, replace_all
Glob always filename pattern match pattern*, path
Grep always ripgrep content search pattern*, path, glob, type, output_mode, -i/-n/-o, -A/-B/-C, multiline, head_limit
Bash always POSIX shell command*, timeout, run_in_background
PowerShell always (Windows) pwsh shell command*, timeout, run_in_background
NotebookEdit deferred edit one Jupyter cell notebook_path, new_source, cell_id, cell_type, edit_mode
Agent always spawn a subagent description, prompt, subagent_type, model, isolation, run_in_background
SendMessage deferred agent-to-agent message to, message, summary
Workflow always (gated: ultracode) multi-agent orchestration script script / scriptPath / name, args
TaskCreate/List/Get/Update deferred todo tracking and dependencies subject/description; taskId; status, addBlockedBy/addBlocks
TaskStop deferred kill background task / agent task_id
TaskOutput deferred (deprecated) read a background job’s output task_id*, block, timeout
CronCreate/List/Delete deferred in-session scheduled prompts cron, prompt, recurring; id*
Monitor deferred stream events from logs / cmds / WebSockets description, timeout_ms, persistent*, command | ws
RemoteTrigger deferred cloud routines (/schedule) action*, trigger_id, body
ScheduleWakeup always (gated: /loop) self-paced loops delaySeconds, prompt, reason, stop
WebFetch / WebSearch deferred fetch a URL / web search url+prompt; query*
PushNotification deferred desktop / phone ping message, status
AskUserQuestion interactive structured question to the user questions[]*
EnterPlanMode / ExitPlanMode interactive read-only planning and approval (none)
EnterWorktree / ExitWorktree deferred (gated: worktree) isolated git worktree name | path; action*, discard_changes
DesignSync deferred claude.ai design-system read/write method*, projectId, planId, writes/deletes, files, assets
Skill always run a slash command skill*, args
ReportFindings always (gated: code-review) typed findings to UI findings[]*, level
Artifact interactive publish HTML/MD as a private page file_path, favicon, url, action
ToolSearch always load deferred tool schemas query, max_results

How this was measured

Three methods, each more authoritative, and no network capture needed. The parameter schemas come straight from the running session.

  • Ask, interactively: "list all your tools with their arguments."
  • Headless, scriptable: claude -p "Introspect and list every tool… output markdown." One thing to know: headless -p exposes a reduced toolset. Artifact, AskUserQuestion, and the plan-mode tools don’t appear (no interactive surface), so add them back by hand.
  • Read the session logs: ~/.claude/projects/<encoded-cwd>/<session-uuid>.jsonl records the roster (tool_reference, deferred_tools_delta, skill_listing, agent_listing_delta) from the harness, independent of the model, though not the argument schemas (inputSchema isn’t in the transcript).

The typed parameters above come from ToolSearch loading each deferred tool’s full JSONSchema, the same definition the CLI would send on the wire, so a packet capture would only show what the session already hands over.


Originally published in TheBigPromptLibrary on GitHub (view source).
⭐ Star the library on GitHub for more system prompts and research.

Your Own Key, Their Telemetry: Reverse-Engineering What Claude Code Phones Home in BYOK Mode

You paid for your own API key. You pointed Claude Code at your own provider. You assumed the conversation was between you and your endpoint. It isn’t — not entirely. Here’s what still flies back to Anthropic, captured off the wire, and the one line of config that shuts it up.

Point-in-time snapshot — capture performed 2026-02-04. Claude Code changes fast; the client version behind this capture is a moment in time, and some internals below have already shifted (e.g. the flag backend moved from Statsig to GrowthBook). Treat this as a dated field report, not a description of current behavior. The [DOC] claims are re-verifiable against live docs; the [WIRE] observations reflect what the client did on 2026-02-04.

Ethics & method. Everything here comes from watching my own machine’s HTTPS traffic with mitmproxy — the same lawful, on-your-own-device inspection any developer can run. Nothing was exfiltrated, decrypted from anyone else, or accessed without authorization. This is transparency research, in the same spirit as my RECon 2024 GPT-reversing work. I flag every claim as [DOC] (Anthropic-documented), [WIRE] (observed in my own 2026-02-04 capture), or [FLAG] (community-observed internal detail, not guaranteed).

The uncomfortable one-liner

When you run Claude Code in Bring-Your-Own-Key mode:

export ANTHROPIC_BASE_URL="https://your-provider.example/v1"
export ANTHROPIC_AUTH_TOKEN="your_api_key"

…your prompts and code go to your provider. Good. But by default, Claude Code still opens side-channels to Anthropic-controlled infrastructure and streams operational telemetry, error traces, and feature-flag lookups — while computing an estimated dollar cost for API calls it isn’t even billing you for. [DOC][WIRE]

None of it contains your source code. All of it is enough to fingerprint you, time you, and profile how you work.

And here’s the part most "privacy" write-ups miss: custom-base-URL BYOK is treated like the direct Anthropic API — telemetry defaults ON. Bedrock, Vertex, and Foundry users get it off by default. Roll your own gateway, and you inherit the chattier posture. [DOC]

What I actually saw on the wire

Capture setup: mitmproxy, HTTPS interception, cert pinning disabled, one trivial task — "create a C hello world, build it with CMake, run it." One boring task. Look how much came back.

Destination Relative volume What it’s for
Anthropic telemetry / log intake (Datadog) many Usage metrics, timing, cost
Feature-flag service several Runtime flag evaluation (tengu_*)
Error-reporting endpoint a few Crash / error stack traces
Your custom provider — The only thing you thought was happening

They know your machine — persistently

Every session carries a stable device fingerprint plus a per-session ID: [WIRE]

// [WIRE] observed shape
{ "device_id": "50xx… (persistent SHA-256)", "session_id": "xxxxxxxx-xxxx-…" }

The session_id rotates. The device_id does not. It’s a durable hash that ties every session you ever run — across projects, across providers, across months — back to the same box. On your own key, with your own provider, you are still a stable, trackable identity to Anthropic. That’s the finding.

They know how you work (not what you write)

The telemetry is metadata-dense and content-free — sizes, types, durations, verdicts. A representative slice of what streams out during a session: [WIRE]

  • Every tool call: name, duration, success/failure, result size — Bash, file writes, todo updates, the lot.
  • Bash commands, classified: not the string, but the type — mkdir, cmake, git, cd — plus exit code and output byte-length.
  • File operations: create/write/delete, hashed path, lines added/removed, char count.
  • Token + cost: input/output/cache tokens and a computed cost_usd — estimated on their side even though your provider is doing the billing. [DOC][WIRE]
  • Timing: time-to-first-token and total duration per call — a latency fingerprint of your provider.
  • BYOK tell: your base_url and model selection can ride along in request telemetry — i.e. Anthropic can see which alternative provider you defected to. [WIRE]

Illustrative shape (structure, not a guaranteed schema):

// ILLUSTRATIVE reconstruction of an observed event
{
  "event": "tengu_tool_use",
  "properties": { "tool_name": "Bash", "duration_ms": 2531, "success": true, "result_length_chars": 45 }
}

Stitch these together and you don’t get my code — you get a behavioral cast of my session: how many tools, how fast, how often I failed, how big my outputs were, which provider I used, and what it cost. Anonymous it is not, given that persistent device_id.

The full event zoo

A single session lit up dozens of distinct tengu_* event types [WIRE][FLAG] — session lifecycle (tengu_session_started / _ended, tengu_request_initialized), API (tengu_api_request, tengu_api_usage, streaming events), tools (tengu_tool_use, tool-result and rate-limit events), bash (tengu_bash_tool_command_executed, tengu_bash_tool_timeout), files (tengu_file_tool_applied, read events), conversation, errors and retries.

"Tengu" — the client’s own nervous system

[FLAG] tengu_ is Claude Code’s internal codename, wired into its runtime feature-flag system (originally Statsig; migrated to GrowthBook after Statsig’s Sept-2025 acquisition). None of it is officially documented; all of it is visible if you watch the client evaluate flags on startup. Community-observed gate names are the spicy part — they read like a changelog of things the client is quietly deciding about you:

  • tengu_attribution_header — request attribution tagging
  • tengu_anti_distill_fake_tool_injection — a countermeasure whose very name suggests anti-distillation / anti-scraping defenses baked into the client
  • tengu_penguins_off — an internal kill-switch of some flavor

You don’t have to speculate about intent to find it notable that your local CLI ships remote-controlled behavior flags with names like "anti-distill fake tool injection." These names are community-observed rather than documented, and they churn between releases — hence the [FLAG] marker on this section.

The redemption arc: it’s all opt-out

Here’s what keeps this fair — and, honestly, more useful than pure outrage. None of this is mandatory. Anthropic documents the switches, and a single one silences telemetry, error reporting, auto-update, feature-flag lookups, and model discovery in one shot: [DOC]

export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1   # the wire goes quiet

Set it and the Datadog / flag / error chatter drops away, leaving only your provider on the wire. The full, tiered hardening config — plus the two settings people think belong here but don’t — is in Lock it down at the end.

What’s private vs. what leaks (default BYOK)

Stays with you / your provider only [DOC][WIRE]

  • Prompts and model responses · file contents and diffs · full command strings (only a coarse type leaks) · real file paths (only hashes leak)

Anthropic’s own docs back the content-exclusion, for the record: [DOC]

"Metrics never include your code, prompts, or file paths."

Leaves to Anthropic-side infra by default [WIRE]

  • Persistent device_id + session IDs · tool-usage metadata · bash command types + exit codes · token counts and estimated cost · TTFT/total timing · your provider base URL + model · redacted error traces

Bottom line

  1. BYOK protects your content, not your metadata. Your code is safe; your behavior and identity are not, by default.
  2. The provider leak is the sharpest finding for BYOK users — Anthropic can see which alternative endpoint you’re routing to.
  3. You are persistently fingerprinted via a non-rotating device_id, even on your own key.
  4. It’s all opt-out — one documented env var silences it. Any critique that omits this is doing outrage, not research.
  5. Trust your capture, not the codenames. The tengu_* internals are undocumented and mutate between releases; publish what your own wire shows.

Lock it down: the hardened BYOK config

Everything above is opt-out. Here’s the config to run. Two equivalent forms — the env block in ~/.claude/settings.json, or plain shell exports (settings.json simply injects these as environment variables). [DOC]

The 80/20, if you read nothing else:

export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1   # the master kill-switch

That one flag already covers telemetry, error reporting, auto-update, feature flags, and model discovery. Everything below is belt-and-suspenders — explicit, self-documenting, and resilient if the master flag’s coverage ever changes.

Tier 1 — Privacy: stop the phone-home

Variable Effect
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC Master switch — telemetry, error reporting, auto-update, feature flags, model discovery.
DISABLE_TELEMETRY Usage / analytics metrics (Datadog).
DISABLE_ERROR_REPORTING Error / stack-trace uploads.
DISABLE_AUTOUPDATER Update-check network calls.
DISABLE_BUG_COMMAND /bug uploads — which do include content.
DISABLE_NON_ESSENTIAL_MODEL_CALLS Background model calls (auto-generated titles, etc.).

Tier 2 — Quality of life (noise, not telemetry)

These quiet the UI; they don’t change what leaves your machine. Bundled in for a clean, distraction-free setup — just don’t mistake them for privacy controls.

Variable Effect
DISABLE_COST_WARNINGS Suppresses cost / usage nag prompts.
DISABLE_PROMOTIONAL_MESSAGES No marketing / upsell messages.
CLAUDE_CODE_DISABLE_TERMINAL_TITLE Stops Claude Code from rewriting your terminal title.

Drop-in ~/.claude/settings.json

{
  "env": {
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
    "DISABLE_TELEMETRY": "1",
    "DISABLE_ERROR_REPORTING": "1",
    "DISABLE_AUTOUPDATER": "1",
    "DISABLE_BUG_COMMAND": "1",
    "DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",

    "DISABLE_COST_WARNINGS": "1",
    "DISABLE_PROMOTIONAL_MESSAGES": "1",
    "CLAUDE_CODE_DISABLE_TERMINAL_TITLE": "1"
  }
}

Same thing as shell exports (no file)

bash / zsh:

# Tier 1 — privacy
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
export DISABLE_TELEMETRY=1
export DISABLE_ERROR_REPORTING=1
export DISABLE_AUTOUPDATER=1
export DISABLE_BUG_COMMAND=1
export DISABLE_NON_ESSENTIAL_MODEL_CALLS=1
# Tier 2 — quality of life
export DISABLE_COST_WARNINGS=1
export DISABLE_PROMOTIONAL_MESSAGES=1
export CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1

PowerShell:

$env:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1"
$env:DISABLE_TELEMETRY                        = "1"
$env:DISABLE_ERROR_REPORTING                  = "1"
$env:DISABLE_AUTOUPDATER                      = "1"
$env:DISABLE_BUG_COMMAND                      = "1"
$env:DISABLE_NON_ESSENTIAL_MODEL_CALLS        = "1"
$env:DISABLE_COST_WARNINGS                    = "1"
$env:DISABLE_PROMOTIONAL_MESSAGES             = "1"
$env:CLAUDE_CODE_DISABLE_TERMINAL_TITLE       = "1"

Toggle-on-presence gotcha. For these flags, any non-empty value — including "0" — turns the disable ON. To re-enable a feature, remove the variable entirely; setting it to "0" will not do what you’d expect.

Tier 3 — Handle with care (you’ll see these in the wild)

Two settings get copy-pasted into "privacy" configs where they don’t belong. Listed here explicitly so you know why they’re not in the block above — not dropped, just quarantined:

  • CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 — this is an enable, and it adds traffic: the client queries your gateway to discover available models. It directly opposes CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, which switches discovery off. Only set it if your BYOK gateway genuinely needs runtime model discovery to function — otherwise leave it out for a quieter wire.
  • DISABLE_PROMPT_CACHING=1 — not a telemetry or privacy control. It turns off prompt caching, which just makes sessions slower and pricier; in BYOK the caching happens at your provider anyway, so it buys you zero privacy from Anthropic. Set it only for a specific reason (e.g. a provider that mishandles cache headers).

Sources for the [DOC] claims: Data usage · Environment variables · Settings · Network configuration · Security.

Lawful, on-device transparency research — a dated snapshot from 2026-02-04. Some internals will have changed since; re-verify against live docs before relying on the specifics.


Originally published in TheBigPromptLibrary on GitHub (view source).
⭐ Star the library on GitHub for more system prompts and research.