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). The plugin works 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 visible only if its files are on disk before that happens. So "install the plugin" 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 takes effect only in the next
session. 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 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. Improvements then ship by merging a PR, not 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 does the actual work.
It installs 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.
The script refreshes the plugin (re-pulls the catalog and runs 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 to know:
cd "$ROOT"in the trampoline is not decoration. Resolving$ROOT, using it only to locate the script, and then not runningcdis an easy and quiet mistake.cloud-setup.sh's paths are relative and its steps are all guarded with|| true. From the wrong directory, the script skips everything and still printscloud setup complete. The script now resolves and validates its own root as a backstop. Thecdkeeps the two independent.- Pasting the body of
cloud-setup.shdirectly still works if you prefer not to delegate. Its root resolution validates each candidate against a marker file. The script 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. 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. It makes Claude check the environment (and the baseline gate result) before Claude starts changing code.
Then run the headless probe in the session. The probe boots a fresh Claude, lists every
available skill, and counts the dev-team:* skills:
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 does not keep the plugin current. Three things pin a stale version:
- The environment filesystem is snapshotted and reused, including
~/.claude/plugins/cache/. The snapshot freezes whatever version installs first and hands that version to every later session. claude plugin installis a no-op on an already-installed plugin. It does not upgrade. Also,marketplace 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. 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). The script setsextraKnownMarketplaces.bfinster.autoUpdate: truein the configsettings.json. The/pluginUI and/upgradetoggle the same flag. 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 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 advisory is the safety net for the one case the refresh cannot fix silently: a restrictive network policy that blocks the catalog/update fetch, or an update that applies only on the next launch. The script says nothing when the plugin is already current. - Manual escape hatch.
/upgradeupdates on demand in any single session.
This setup does not do two things:
- It does not touch your local machine. Both scripts run only in cloud
contexts. You paste the Setup script into the cloud UI. The SessionStart
hook is gated on
DEV_TEAM_CLOUD_INSTALL=1(unset locally by design). Neither script edits your local~/.claude/settings.json. To get the same auto-update behavior on your own machine, run the following 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 someone cuts it. 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. Do not block on anything that can hang.
- Snapshot rebuild triggers. Later sessions reuse the snapshotted Setup-script filesystem. Editing the Setup script (or other environment config) triggers a rebuild on the next session. That is 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. The platform discards anything you do not commit and push when it reclaims the container.
SessionStarthook is fallback only..claude/install-dev-team.sh(gated byDEV_TEAM_CLOUD_INSTALL=1) installs the plugin too late for the current session. The plugin lands next session. Use the Setup script for same-session loading.