Cloud setup: loading the dev-team plugin into a web session¶
This is the focused recipe for making the dev-team@bfinster plugin work in a
Claude Code on the web session (claude.ai/code) — in the same session,
not the next one. For the broader walkthrough (including the file-based
fallback), see
using-plugin-skills-in-the-web-environment.md.
Why declaring the plugin isn't enough¶
Claude loads every skill, agent, and slash command once, when it starts. A plugin is only visible if its files are on disk before that happens. So "install the plugin" really means "get the plugin on disk before Claude starts."
That timing is the whole story:
| Mechanism | Runs… | Plugin loads… |
|---|---|---|
| Setup script (cloud UI) | before Claude boots; filesystem snapshotted & reused | this session ✅ |
SessionStart hook (.claude/install-dev-team.sh) |
after Claude boots | next session only ⚠️ |
The Setup script is the supported way to install software before the session
starts (you cannot replace the underlying machine image). The SessionStart
hook runs too late — the plugin it installs only takes effect in the next
session — so use the Setup script as your primary path and the hook as a fallback.
The claude CLI is available in cloud environments, so the install commands
below run fine from the Setup script.
The snippet to paste into the Setup script field¶
claude.ai/code → Environment → Setup script. Paste this delegating trampoline — it keeps the UI field stable and lets the real logic live in the repo, so improvements ship by merging a PR rather than by editing environment config and forcing a snapshot rebuild:
#!/bin/bash
set -uo pipefail
ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
cd "$ROOT" || { echo "[env-setup] cannot cd to $ROOT — skipping setup."; exit 0; }
SETUP="$ROOT/.claude/cloud-setup.sh"
if [ -f "$SETUP" ]; then
# Always exits 0 by design — a non-zero Setup script FAILS session startup.
# Read its ✗ lines (from scripts/verify_toolchain.py) for real failures.
bash "$SETUP"
else
echo "[env-setup] MISSING: $SETUP — repo hasn't adopted the dev-team cloud setup."
npm ci || true # fallback so the husky git hooks still land
fi
exit 0
.claude/cloud-setup.sh is the actual work:
this repo's test/gate toolchain (jq, shellcheck, the Python dev deps, gh,
uv, mutmut, adr), Node 24+ plus npm ci for the git hooks
(pre-commit, pre-push, commit-msg), the plugin install/refresh, and a
closing scripts/verify_toolchain.py pass.
It refreshes the plugin (re-pulls the catalog and plugin update) rather
than only installing — plugin install is a no-op once a version is cached, and
the reused snapshot would otherwise pin the first version forever (see
Keeping the plugin version current).
Two details worth knowing:
cd "$ROOT"in the trampoline is not decoration. Resolving$ROOT, using it only to locate the script, and then notcd-ing there is an easy mistake — and a quiet one.cloud-setup.sh's paths are relative and its steps are all guarded with|| true, so from the wrong directory it skips everything and still printscloud setup complete. The script now resolves and validates its own root as a backstop, but thecdkeeps the two independent.- Pasting the body of
cloud-setup.shdirectly still works if you'd rather not delegate. Its root resolution validates each candidate against a marker file, so it detects that it is running from a pasted temp file and falls back to the working directory. You then give up the ship-by-merge property above.
Verify it worked¶
The Setup script must exit 0 even when provisioning went wrong, so it can only
report a broken toolchain — it can never refuse to hand one over. Two things
close that gap:
scripts/verify_toolchain.py— runs every tool instead of probingPATH, so an installed-but-unstartable tool fails instead of passing. The Setup script calls it at the end; run it yourself any time withpython3 scripts/verify_toolchain.py.cloud-startup-prompt.md— the first message to send in a fresh session, so Claude checks the environment (and the baseline gate result) before it starts changing code.
Then run the headless probe in the session. It boots a fresh Claude, lists every
available skill, and counts the dev-team:* ones:
claude -p "List the names of every skill available to you, one per line." \
--max-turns 1 | grep -c '^dev-team:'
| Setup | dev-team:* skills |
dev-team:ship present? |
|---|---|---|
| Plugin installed via Setup script (pre-boot) | ~86 | yes ✅ |
Plugin installed via SessionStart hook only (post-boot) |
0 (this session) | no ⚠️ |
| No install | 0 | no |
A non-zero count means the plugin loaded this session. (Re-verified in a live
cloud session on 2026-06-21: CLI present at v2.1.185, Setup-script install
yielded 86 dev-team:* skills including dev-team:ship, loaded same-session.)
Keeping the plugin version current¶
Installing once is not enough to stay current. Three things conspire to pin a stale version:
- The environment filesystem is snapshotted and reused — including
~/.claude/plugins/cache/. Whatever version installs first is frozen into the snapshot and handed to every later session. claude plugin installis a no-op on an already-installed plugin — it does not upgrade — andmarketplace addon a known marketplace does not re-pull the catalog.- Auto-update is off by default, so the CLI never refreshes on its own.
The fix has three layers, all wired into .claude/cloud-setup.sh and the
.claude/install-dev-team.sh SessionStart hook:
- Refresh, don't just install. Both run
claude plugin marketplace update bfinsterandclaude plugin update dev-team@bfinsteron every invocation, and the SessionStart hook no longer no-ops when a version is already present. - Enable marketplace auto-update. Both call the plugin's own
skills/upgrade/scripts/enable_autoupdate.py(--enable), which setsextraKnownMarketplaces.bfinster.autoUpdate: truein the configsettings.json— the same flag the/pluginUI and/upgradetoggle. This is the key lever: it makes the CLI re-pull and upgrade at launch, within the existing snapshot, so a routine release lands without a snapshot rebuild./upgraderuns the very same script (its--check/--enablemodes), so there is one implementation of the flag, not two. - Drift advisory. Both also run the plugin's
skills/upgrade/scripts/check_version_drift.py, which compares the installed version against the refreshed catalog and surfaces a "v{installed} → v{latest}; restart or run/upgrade" advisory. This is the safety net for the one case the refresh can't fix silently: a restrictive network policy that blocks the catalog/update fetch, or an update that only applies on the next launch. It says nothing when already current. - Manual escape hatch.
/upgradeupdates on demand in any single session.
Two things this does not do:
- It does not touch your local machine. Both scripts run only in cloud
contexts — the Setup script is pasted into the cloud UI, and the SessionStart
hook is gated on
DEV_TEAM_CLOUD_INSTALL=1(unset locally by design). Neither edits your local~/.claude/settings.json. To get the same auto-update behavior on your own machine, run it once locally:/upgrade(consent to auto-update) orpython3 plugins/dev-team/skills/upgrade/scripts/enable_autoupdate.py --enable. - It cannot outrun releases. "Current" means the latest released tag
(
marketplace.json'ssource.ref, which release-please bumps on merge). A missed release makes every session lag until it is cut — that is a release concern, not an install one.
Caveats¶
- Always
exit 0. A non-zero Setup script fails session startup. Guard every optional step with|| trueand end with an explicitexit 0. - Time budget. The Setup script has a few-minute budget. Keep installs best-effort and time-boxed; don't block on anything that can hang.
- Snapshot rebuild triggers. The Setup-script filesystem is snapshotted and reused by later sessions. Editing the Setup script (or other environment config) triggers a rebuild on the next session — that's how an updated script takes effect.
- Network policy. A restrictive outbound policy can block
marketplace add/install(orpip/apt). When that happens, run skills from their files instead — see Option B in the companion doc. - Ephemeral VM. Anything not committed and pushed is lost when the container is reclaimed.
SessionStarthook is fallback only..claude/install-dev-team.sh(gated byDEV_TEAM_CLOUD_INSTALL=1) installs the plugin too late for the current session; it lands next session. Use the Setup script for same-session loading.