Claude Code web and CLI have different trust models — git push is branch-locked, machine memory is dropped, and headless CI needs two mechanisms
Claude Code web and CLI have different trust models — git push is branch-locked, machine memory is dropped, and headless CI needs two mechanisms
What you expect
Claude Code documentation presents the web and CLI surfaces as the same product with a different interface. The web overview lists features side by side. You would expect that code behavior, session state, and CI patterns that work in CLI work the same way in web, with minor UI differences.
You would also expect --dangerously-skip-permissions to suppress all startup dialogs in headless CI — that is what a “skip permissions” flag reads as.
What actually happens
Web and CLI are not the same runtime with a different UI. They have different trust models, data flows, and capability ceilings.
The detection primitive
CLAUDE_CODE_REMOTE=true is set in every web session and unset in CLI. The hooks documentation confirms this as the detection primitive: “The $CLAUDE_CODE_REMOTE environment variable is set to true in remote web environments and not set in the local CLI.” Hook scripts must gate on this variable to distinguish surfaces.
VM isolation and the trust boundary
Web sessions run in an Anthropic-managed VM. All GitHub operations go through a scoped-credential proxy — your actual GitHub token never enters the sandbox. The web docs describe it as “Isolated virtual machines” with a “scoped GitHub credential proxy.”
CLI runs on your local machine. Files stay local. Only API inference calls leave your machine. These are not equivalent trust boundaries.
Git push is locked to the session-initialization branch
This is the constraint that surprises practitioners who assume only main is protected. The web docs state: “Restricts git push operations to the current working branch for safety.” All push targets other than the exact branch the session was initialized on return 403 — including new arbitrary branches created mid-session. If you need to push to any other branch, start a new session initialized on that branch, or use /teleport to pull the session back to your local CLI.
~/.claude/MEMORY.md is absent from the web VM
The memory docs confirm: “Auto memory is machine-local… Files are not shared across machines or cloud environments.” The web VM has no access to ~/.claude/MEMORY.md. The availability table also shows user-level ~/.claude/CLAUDE.md is absent (“Lives on your machine, not in the repo”).
What survives cross-surface transitions (committed to repo): CLAUDE.md, .claude/agents/, .claude/settings.json, git history.
What does NOT survive: ~/.claude/MEMORY.md, settings.local.json, MCP servers configured locally but not in .mcp.json.
MCP hot-reload does not work mid-session
The web docs list .mcp.json MCP servers as “Part of the clone” — they load at session-start only. The MCP docs mention list_changed notifications for dynamic tool updates, but this applies to already-connected servers updating their tool lists, not loading new servers added to .mcp.json mid-session. Adding a server to .mcp.json during a web session requires starting a fresh session.
Headless CI requires two mechanisms — the flag alone is insufficient
--dangerously-skip-permissions suppresses interactive tool-use approval prompts during a session. It does NOT suppress the startup “WARNING: Bypass Permissions mode” dialog. Without a second control, every claude invocation in CI blocks on the startup dialog.
Issue #25503 (“—dangerously-skip-permissions flag should bypass the permissions mode dialog without requiring persisted setting”) has been open since February 2026 (filed 2026-02-13) and remains open as of 2026-06-27.
The two controls required together:
- CLI flag:
--dangerously-skip-permissions— suppresses tool-use prompts - settings.json key:
skipDangerousModePermissionPrompt: true— suppresses the startup dialog
Possible additional flag bug (CLI, unverified): In one observed session, mid-session bypass mode appeared to silently reset after a “Sibling tool call errored” cascade in a parallel tool batch, with subsequent tool calls prompting for approval despite the flag being active. This is a single-session observation, not reproduced or independently confirmed — the closest matching public report, closed issue #51632, describes a different (VS Code extension) context and does not clearly match this mechanism. Treat as unverified; if you hit this, avoiding parallel tool batches in CI is a plausible (untested) workaround.
What is the same across surfaces
These work identically in web and CLI (confirmed via web docs): CLAUDE.md loading, hooks (all events including PreToolUse), subagents via Task tool (truly concurrent processes sharing /tmp), repo-level .claude/agents/, and CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS from .claude/settings.json.
What this means for you
If you’re planning to use web sessions in CI: Web sessions have no headless invocation path. GitHub Actions and scripted automation require CLI.
If you rely on ~/.claude/MEMORY.md for session context: That context is invisible in web sessions. Switching to web starts fresh unless the information is committed to the repo.
If you’re automating branch workflows in web sessions: You cannot push to arbitrary branches. The session’s git push scope is locked at initialization time.
If you’re running headless CI with just —dangerously-skip-permissions: Your pipeline will block on the startup dialog unless you also have skipDangerousModePermissionPrompt: true in settings.json.
Capabilities that are CLI-only: Bedrock, Vertex, and LiteLLM providers; GitLab and non-GitHub repos; Unix pipe composability (tail -f log | claude -p "..."); claude mcp serve; machine-local MEMORY.md.
Capabilities that are web-only: Async execution after browser close; built-in PR diff view; multiple parallel sessions (each gets its own VM); iOS monitoring; session sharing links; /teleport to pull a web session to your local terminal.
What to do
- Detect the surface at runtime using
$CLAUDE_CODE_REMOTE. Hook scripts should gate local-only setup on this variable being unset. - Commit everything that must persist to the repo.
~/.claude/MEMORY.md, local settings, and locally-configured MCP servers do not survive cross-surface transitions. - For headless CI, pre-seed
~/.claude/settings.jsonwith both controls before invokingclaude -p:mkdir -p ~/.claude echo '{"skipDangerousModePermissionPrompt":true,"permissions":{"defaultMode":"bypassPermissions"}}' \ > ~/.claude/settings.json claude -p "$PROMPT" --dangerously-skip-permissions - Design branch workflows before starting a web session. You cannot push to a branch other than the one the session was initialized on.
- Consider the official
anthropics/claude-code-action@v1GitHub Action instead of rawclaude -pinvocations. It may absorb some of the two-mechanism CI problem, but we have not verified this against its source or docs — confirm its handling of the startup dialog before relying on it, or configure both controls yourself as shown above. - Commit
.mcp.jsonbefore starting any web session that needs MCP servers. There is no mid-session hot-reload path.
Falsification criterion: This finding would be disproved by a Claude Code web runtime update that (1) allows git push to branches other than the session-initialization branch, or (2) loads ~/.claude/MEMORY.md from the user’s machine into the web VM, or (3) makes --dangerously-skip-permissions suppress the startup dialog without a persisted settings.json key (which would also close Issue #25503).
Evidence
All rows below document the same model (Claude) running in different deployment environments (web runtime vs CLI) — this is not a cross-model comparison, and results are directly comparable across rows.
| Tool | Version | Evidence | Result |
|---|---|---|---|
| Claude Code docs (hooks) | v2.1.195 (2026-06-27) | docs-reviewed | $CLAUDE_CODE_REMOTE confirmed as web/CLI detection primitive |
| Claude Code docs (web) | v2.1.195 (2026-06-27) | docs-reviewed | Web = Anthropic-managed VM; git push restricted to current working branch; MCP loads from .mcp.json at session start only |
| Claude Code docs (memory) | v2.1.195 (2026-06-27) | docs-reviewed | ”Auto memory is machine-local… not shared across machines or cloud environments” — ~/.claude/MEMORY.md absent in web VM |
| anthropics/claude-code Issue #25503 | open as of 2026-06-27 | independently-confirmed | Bug confirmed open: —dangerously-skip-permissions flag insufficient alone; startup dialog requires persisted setting |
Confidence: empirical — 3 documentation surfaces reviewed (hooks, web, memory docs), confirmed against current v2.1.195 docs (2026-06-27); 1 independently confirmed open GitHub issue (#25503).
Strongest case against: The git push branch restriction, the MEMORY.md absence, and the MCP session-start-only loading are all explicitly documented behaviors — they are intentional design constraints, not undocumented surprises. A practitioner who reads the web docs carefully before building will encounter all of these. The headless CI two-mechanism issue is an acknowledged bug (Issue #25503) that could be closed before a reader sees this finding. This finding’s value is consolidating scattered constraints into one place, not uncovering undisclosed behavior.
Open questions: Whether CLAUDE_CODE_EFFORT_LEVEL works in web sessions (unconfirmed in current docs); whether the mid-session bypass reset after parallel tool errors described above is a real, reproducible bug or a one-off (it remains unverified — no independent confirmation found); whether environment caching for SessionStart hooks applies to resumed sessions as well as fresh starts.
Seen different? Contribute your evidence — share a repro or counter-example and we’ll review it against this finding. Reader evidence is what keeps these findings accurate.