Skip to content

Cloud environment: from nothing to a working claude --cloud session

How an adopter gets claude --cloud sessions on their own fork of himmel starting with the harness working. The cloud lane runs small, well-scoped tickets on an Anthropic-managed VM and opens a PR; a local shepherd then reviews and merges it. What a session has, and why the shepherd is mandatory, is in cloud-brief-template.md. Platform reference: cloud environments.

Why this is a paste, not a command

Checked against the docs and the CLI on 2026-10-10:

QuestionAnswerSource
Create or declare an environment from a repo file?No. The dialog is the only path; the setup script is “entered in the Setup script field” of the environment settings dialogcloud-environments
Create one from a command or API?No. /remote-env “only sets the default: it doesn’t start a session, and it can’t add or edit environments”. claude --help has no environment subcommandsame page; claude --help (2.1.296)
Read an environment’s script or variables back from the CLI?No. /remote-env lists names and IDs onlysame page
Detect that the dialog differs from the repo?No platform mechanism. A session sees CLAUDE_CODE_REMOTE=true and the variables you setsame page

The Managed Agents environments API (POST /v1/environments, ant apply environment.yaml) is a different product: API-key billed, its own sessions, no env-var or setup-script field, and nothing connects it to claude --cloud. It does not apply here.

So himmel keeps the dialog’s content in one checked-in source and checks the dialog against it:

FileRole
scripts/cloud/environment.envThe environment variables and the setup-script # rev:
scripts/cloud/setup-env.shThe setup script itself; the dialog only clones the repo and runs it
scripts/cloud/check-env.sh--print renders the dialog’s fields; with no argument a session checks itself

Prerequisites

  • A Claude subscription with Claude Code on the web (claude.ai/code), and the claude CLI on your machine.
  • Your fork of himmel on GitHub, with the Claude GitHub app installed on it: at claude.ai/code connect GitHub and grant the app access to the fork, or run /web-setup in a local terminal. The platform’s GitHub proxy then authenticates git and gh inside the VM, so no token of yours enters it.
  • The fork’s origin set on the checkout you run the commands below from.

Create the environment

  1. From your fork’s checkout, print the fields (it uses your origin as the clone URL, so a fork gets its own URL):

    Terminal window
    bash scripts/cloud/check-env.sh --print
  2. At claude.ai/code, select the cloud icon above the message box, then Cloud, then Add cloud environment. Paste the printed fields: Name himmel, Network access Trusted, Environment variables (the printed block), Setup script (the printed block).

Notes on the fields:

  • Everyone who uses the environment can read the variables, so never put a secret there. The cloud has no Jira token on purpose: Jira goes through the claude.ai Atlassian connector, not the local jira CLI.
  • Leave GH_TOKEN and GITHUB_TOKEN unset so the GitHub proxy authenticates.
  • The two Bash timeouts must be set in this field: it is the only place they reach the Bash tool.
  • Keep the setup script’s trailing || true: a non-zero setup script stops the session from starting, and a failed step should cost a tool, not the session.
  • The setup script installs shellcheck, at and pre-commit, builds the Jira CLI dist, installs the obsidian-triage deps and the lean plugin profile (himmel-ops, lean-skills; a claude.ai plugin upload never loads in a cloud session), then graphify and qmd over the repo. Each build is bounded by timeout and a failure costs only that tool. Its last step stamps the script’s hash for the self-check below.
  • Network access Trusted covers GitHub, npm, PyPI and the Ubuntu archives, which is everything the setup and a session reach. Atlassian needs no entry: connector traffic goes through Anthropic, not the VM’s network. Pick Custom only for a host outside that list, and tick “Also include default list of common package managers”.

Make it the CLI default

claude --cloud does not use the claude.ai selector. In a local terminal run /remote-env once and pick himmel; it saves remote.defaultEnvironmentId in your user settings. Without it the CLI falls back to the Anthropic-hosted Default environment, which has no setup script.

First session

Terminal window
claude --cloud "reply with the word ready, change nothing"

The first session runs the setup script (about five minutes); watch it at the session URL the command prints. If it finishes inside about five minutes the platform snapshots the filesystem and later sessions start from the snapshot.

Verify it

In the session, run:

Terminal window
bash scripts/cloud/check-env.sh

Expect env <NAME> ok for each variable and setup-script ok, exit 0. A mismatch is named on its own line and exits 1:

LineMeaningFix
env <NAME> MISMATCH expected=... actual=...The dialog’s variable differs from environment.envFix the dialog from check-env.sh --print, then start a new session (a resumed one keeps the values it last read)
setup-script STALEThe cached snapshot ran an older setup-env.sh than this clone’sBump # rev: in the dialog’s setup script (and in environment.env) to rebuild the snapshot
setup-script MISSINGNo stamp: the setup never finished, or the dialog does not run setup-env.shRead /tmp/himmel-setup-logs/ for the failed step, or re-paste the setup script

What the check cannot see: the bootstrap text in the dialog (the clone URL and flags) is not readable from inside the VM, so a changed URL shows only as a missing or stale stamp. The check is also not wired into a SessionStart hook yet: a cloud brief or the operator runs it by hand.

Then the tool probe:

Terminal window
echo CLAUDE_CODE_REMOTE=$CLAUDE_CODE_REMOTE BASH_DEFAULT_TIMEOUT_MS=$BASH_DEFAULT_TIMEOUT_MS
shellcheck --version | sed -n 2p
ls ~/.claude/plugins
# Inside the session's repo worktree, not /tmp/himmel-setup:
bash scripts/cloud/setup-env.sh
graphify query "cloud route classification" --graph graphify-out/graph.json
bash scripts/lib/qmd-bounded.sh collection list
bash scripts/lib/qmd-bounded.sh search "cloud environment" -c himmel

Expect CLAUDE_CODE_REMOTE=true, BASH_DEFAULT_TIMEOUT_MS=600000, a shellcheck version, a plugins directory, a Graph: ... nodes line, exactly one collection (himmel) and hits from docs/. Then ask the session to list its skills: the himmel-ops: and lean-skills: skills should be there.

What the cloud has, and what stays local

The cloud hasHow
HooksThe repo’s .claude/settings.json; plugin hooks through the setup script’s plugin step (probed)
graphifyPinned at scripts/lib/graphify-bin.sh, no backend extra, AST-only (graphify update .): it parses code locally and calls no model
qmd, repo onlyThe pinned fork and one collection, himmel, rebuilt on every setup run. BM25 only; a ticket that needs qmd query, vector search or another collection routes LOCAL-NATIVE
JiraThe claude.ai Atlassian MCP connector, enabled for the session
Stays localWhy
luna and any other vaultPrivate vault data never leaves the station (scripts/guardrails/egress-matrix.json)
Handover stateIt lives in the state repo, which the cloud cannot reach; a cloud session reports through its PR
A Jira tokenNot in the environment on purpose; use the Atlassian connector
Lane tickets (claudex, codex or another lane)Cloud is Claude only; /cloud-route routes them LOCAL
The chief bridge and the hook-integrity off switchLocal launching-shell state; a ticket that edits scripts/hooks/ routes HOOK-BYPASS

The cached graph and index live in /tmp/himmel-setup, a frozen clone. In the session’s own worktree run bash scripts/cloud/setup-env.sh to rebuild both against that checkout. Never run a semantic /graphify extraction in the cloud: it would send content to a model backend.

Local-versus-cloud parity

bash scripts/cloud/parity-probe.sh is a read-only probe: one item<TAB>value row per item under ## environment, settings, plugins, guards, gates, tools, jira and egress. Run it locally and in a cloud session and diff the two (--no-net skips the egress rows). Each PreToolUse guard row is a harmless input the guard must refuse and reads DENY; ALLOW means that guard is off in that environment. The probe changes nothing.

To run it in the cloud, start one session on the branch with the brief: run bash scripts/cloud/setup-env.sh, then bash scripts/cloud/parity-probe.sh, and post the full output as one top-level PR comment whose first line is CLOUD-DONE <session URL>.

ItemLocalCloud (measured)Gap and fix, or reason and ticket
Repo hooks (10 events, 40 commands)wired, all scripts presentidentical, all scripts presentnone
Repo allow / deny rules156 / 37156 / 37, same deny-shanone
User-scope deny rules and user CLAUDE.md36 deny, present0 deny, absentNot closed here: the template would come from private user settings and rewriting live settings is VM-first. HIMMEL-5445
Plugin set and plugin hooks12 installed, 81 hook fileshimmel-ops, lean-skills; 2 hook filesBy design: the lean profile installs at setup time. The PreToolUse guards below are the repo’s own hooks, not plugin hooks. HIMMEL-4206
PreToolUse guards (9 deny probes)all DENYall DENYnone
pre-commit binary4.6.24.6.2none
Git gate hooks (pre-commit, commit-msg, pre-push)installedclosed here: a fresh clone had none; step git-hooks installs all threeThe hook environments are fetched on first use, not at setup, to fit the ~5 min snapshot
Attestation and ticket-ID gaterefuses a ticketless message (rc 1)same, rc 1none
bun1.4.41.4.2Image version, no action
node, jq, gh, git24.21.0, 1.8.2, 2.102.0, 2.56.022.22.0, 1.7, 2.89.0, 2.43.0Image versions lag the station; the probe and its checks ran on both. No action
gitleaks, HIMMEL_REPOpresentclosed here: step gitleaks of setup-env.sh installs gitleaks 8.30.1, sha256-pinned to the CI release; HIMMEL_REPO not setHIMMEL_REPO: HIMMEL-4580
shellcheck, atpresentpresentnone
graphifypinned 0.9.80 (the pin at probe time; scripts/lib/graphify-bin.sh holds the current pin)image had 0.9.77, graph built AST-only. Closed here: setup skipped any present graphify; it now reinstalls one that differs from the pin, and the probe’s graphify-at-pin row names a driftBuild time against the budget: HIMMEL-4581
qmd2.8.3 on PATH, 11 collectionsinstalled by setup, no shim on PATH, so the probe read <absent>. Closed here: the probe resolves it through qmd-bounded.sh like setup does. One collection, himmel, BM25 onlyBy design: vaults and handover state stay local; a ticket needing a model-backed qmd query or vectors routes LOCAL-NATIVE
Jira accesslocal jira CLIAtlassian MCP connector; the CLI dist is built but there is no tokenBy design: no token in the environment
Bash timeoutsunset locally600000 / 600000, set through the dialogBy design, see Notes above; check-env.sh reports a mismatch
Egressunrestrictedpypi, npm, ubuntu archive 200; example.com 000 (outside the Trusted list, by design); a HEAD to github.com got 400 from the GitHub proxyClosed here: the probe uses GET
luna, vaults, handover state, chief bridge, hook-integrity off switchpresentabsentBy design, local only; two-way chief channel: HIMMEL-4277

The cloud column is one cloud run of the probe on this change’s branch: setup-env.sh, parity-probe.sh and check-env.sh all exited 0.

How a cloud PR lands

  1. /cloud-route classifies a ticket CLOUD-OK, writes the brief and prints the launch line; you run it (claude --cloud "<brief>").
  2. The session works in a worktree, opens the PR and posts one top-level comment whose first line is CLOUD-DONE <session URL>, then the head SHA and test results. A block it cannot resolve is posted as CLOUD-BLOCKED <session URL>.
  3. A local shepherd takes it from there: bash scripts/handover/chief-kit/shepherd.sh <pr> runs the coverage lint, the impacted suites, CI and the ready check; /pr-check and the CR gate run locally, and the chief merges on GO. The cloud session never merges and stops pushing once the shepherd comments.
  4. The ticket stays In Progress until the merge closes it.

Cache and refresh

  • The snapshot is rebuilt when the setup script text or the allowed network hosts change, and when it expires after about seven days. Plugins come from the frozen /tmp/himmel-setup clone, so a change on main reaches new sessions only after a rebuild: bump # rev: in environment.env, re-print and re-paste the script.
  • A paused session that resumes does not re-run the setup script and keeps the variables it last read. Start a new session to pick up a change.