OpenClaw Client Bootstrap
Create and apply a reusable client kit for OpenClaw with strict governance:
- private gateway exposure through Tailscale
- Tailnet-only SSH with non-root logins by default
- Telegram notification channel (sends portal links for approval events)
- SPAPS approval backend with Unclawg portal for human review
- read-only credentials in OpenClaw
- explicit write-gateway contract for all external mutations
AI Runtime-First Skill Split
Treat tracked skills as AI runtime instructions, and keep operator/admin runbooks private.
- Runtime skills must be wrapper-only (
tools.exec.security: "allowlist"+safeBins). - Runtime skills should fail closed when required wrapper bins are missing.
- Keep local project overlays in
modes/(gitignored). - Keep admin-only skills (for example
unclawg-admin) gitignored/private. - Keep deploy secrets gitignored/private. If you automate runtime sync in CI, keep host keys, SSH material, and live deploy targets in your own secret store. Do not commit live-instance workflows to the public repo.
- Do not point a production claw at
../skillswholesale; curate a runtime-safe subset.
Architectural Decision First
Before collecting inputs or building a kit, decide:
New droplet (Option A) — full isolation, separate Tailscale node, separate billing. Best for client deployments or high-volume autonomous claws.
Existing droplet, new Docker container (Option B) — co-located, separate Telegram bot, separate agent key, separate SPAPS agent. Best for purpose-specific internal claws added to an existing box (e.g., a content-creation agent alongside an ops agent). Saves ~$12-18/mo.
See references/deployment-workflow.md — "Architectural Decision" section for isolation guarantees, trade-offs, and Option B deploy steps (skip bootstrap-do.sh + Tailscale, use a different systemd service name).
Also decide storage before building the kit: root disk (default, free), DO Block volume per claw ($1-2/mo, detachable — best if you may migrate later), or shared volume with per-claw subdirs ($1/mo for co-located claws). See "Storage Decision" section in the same file.
On Trigger: Instantiate the Kit
Resolve the bundled generator script, then create a client kit directory.
GEN="$(ls ~/.codex/skills/openclaw-client-bootstrap/scripts/new_client_kit.sh ~/.claude/skills/openclaw-client-bootstrap/scripts/new_client_kit.sh ./scripts/new_client_kit.sh 2>/dev/null | head -1)"
bash "$GEN" --dest /tmp/<client>-openclaw --interactive
If ./scripts/... is used, run the command from the skill directory.
Required Inputs Before Deployment
Collect these before touching the droplet:
- Client name and environment label
- Telegram operator user ID allowlist
- Telegram group chat ID (and optional topic ID)
- Telegram bot token (from BotFather)
- SPAPS credentials (API URL, API key, agent ID, agent secret)
- Unclawg portal URL
- Tailscale auth key and node hostname
- Integration inventory with read-only scopes
Reference: references/deployment-workflow.md
Customize the Kit
- Fill
.envvalues (including SPAPS credentials). - Replace Telegram placeholders in
openclaw.json:{{TELEGRAM_ALLOWED_USER_ID}}{{TELEGRAM_GROUP_CHAT_ID}}- Optional: add
topicIdper group entry when topic-scoped. - Keep
approvals.exec.targets[*].toconcrete (numeric ID or explicit chat ID), not${env:...}.
- Review
SOUL.md,AGENTS.md, andUSER.mdfor client-specific constraints. - Keep
security/WRITE_GATEWAY_CONTRACT.mdintact unless compliance requires stricter rules. - For any new endpoint + skill combo, follow
security/PERMISSIONS_PLAYBOOK.md(wrapper + allowlist pattern).
Review / Grade Implementation
Run the review script for a second-pass quality check against the review rubric:
REV="$(ls ~/.codex/skills/openclaw-client-bootstrap/scripts/review_kit.sh ~/.claude/skills/openclaw-client-bootstrap/scripts/review_kit.sh 2>/dev/null | head -1)"
bash "$REV" --skill # review the skill template
bash "$REV" /tmp/<client>-openclaw # review a generated kit
bash "$REV" --live # SSH into droplet and review live deployment
bash "$REV" --live openclaw@<tailscale-ip> # specific host
bash "$REV" --live --host <tailscale-ip> --ssh-user aiops # dedicated collab login
bash "$REV" --live --service openclaw-<claw>.service --home /home/<claw-user>/.openclaw --user openclaw
bash "$REV" --live --strict # production gate: missing SPAPS/portal vars fail
Three modes:
--skill/ default: 44 file-based checks across config, env, scripts, docs, generator, validator/path/to/kit: same checks on a generated kit--live: SSHes into the droplet viareview_live.shand runs 30+ checks against the running deployment (service health, Node/Docker, live config schema, SPAPS/portal connectivity, Tailscale, security posture, recent logs). Auto-detects host from localreferences/deployed-instances.mdwhen present.
Talking to Agents via CLI (Debug & Bug-Fixing)
Use the bundled scripts/talk.sh for all agent interaction, log tailing, and SSH access. It reads references/deployed-instances.md automatically — no manual env var wrangling.
TALK="$(ls ~/.codex/skills/openclaw-client-bootstrap/scripts/talk.sh \
~/.claude/skills/openclaw-client-bootstrap/scripts/talk.sh \
./scripts/talk.sh \
2>/dev/null | head -1)"
bash "$TALK" --list # see all claws + live agent IDs
bash "$TALK" --message "what work is queued?" # message primary claw
bash "$TALK" --claw my-claw --message "hello" # message a co-located claw
bash "$TALK" --claw example-claw --message "follow up" --session-id <id> # continue thread
bash "$TALK" --claw example-claw --tail # tail logs live (Ctrl-C to stop)
bash "$TALK" --claw example-claw --logs 100 # last 100 log lines
bash "$TALK" --health # health summary for all claws
bash "$TALK" --health --claw example-claw # health summary for one claw
bash "$TALK" --health --emit-logs --log-dir ~/.openclaw/logs --log-prefix openclaw # health + persisted snapshots
bash "$TALK" --ssh # plain SSH into the droplet (non-root)
bash "$TALK" --ssh-user aiops --ssh # use dedicated collab user
bash "$TALK" --claw example-claw --ssh # SSH with claw env pre-loaded
Positional shorthand: talk.sh example-claw "message" is equivalent to --claw example-claw --message "message".
Agent IDs vs persona names: The --agent value (auto-discovered from agents list) is the agent config ID like content-creator. The persona name lives in SOUL.md — these are different. --list shows both.
Co-located claws: talk.sh handles the OPENCLAW_STATE_DIR / OPENCLAW_CONFIG_PATH env var override automatically. --ssh --claw <name> drops you into a shell with those vars already exported.
Gateway 1006 fallback: If the gateway WebSocket closes with code 1006, openclaw agent falls back to embedded mode. The response is still valid — but check journalctl -u <service> -n 50 to investigate the underlying issue.
Additional flags: --thinking medium for more deliberate reasoning, --agent <id> to override auto-discovery, --json for machine-readable --health output.
For local integration workflows, use:
--emit-logs, --log-dir, and --log-prefix to persist remote claw health snapshots.
Provider/Auth Rotation (Codex/OpenAI/OpenRouter/Anthropic)
Use scripts/update-oauth-token.sh as the single path for credential updates across co-located claws.
# Codex OAuth (default): ~/.codex/auth.json -> OPENAI_API_KEY
bash scripts/update-oauth-token.sh --dry-run
bash scripts/update-oauth-token.sh
bash scripts/update-oauth-token.sh --ssh-user aiops
# Direct OpenAI API key (preferred when OAuth is org-restricted)
OPENAI_API_KEY=sk-proj-... bash scripts/update-oauth-token.sh --openai
# OpenRouter key
OPENROUTER_API_KEY=sk-or-v1-... bash scripts/update-oauth-token.sh --openrouter
# Anthropic swap (stdin preferred)
echo "sk-ant-..." | bash scripts/update-oauth-token.sh --anthropic
What this script now enforces:
- Updates shared env (
<openclaw-home>/shared.envby default) and each claw.env - For Codex, writes canonical
auth-profiles.jsonstore (version/profiles/order) foropenai-codexunder every agent dir - Warns on model/provider mismatch (
openai|openai-codexvsopenrouter/*vsanthropic/*) - Warns if
agents.defaults.model.reasoningEffortexists (can triggerUnknown config keyson this runtime)
If you hit OpenAI scope errors like Missing scopes: api.responses.write, switch from --codex to:
--openaiwith a direct OpenAI API key, or--openrouterwithopenrouter/*model IDs.
Do not hand-edit auth-profiles.json unless diagnosing parser behavior.
Deterministic Recovery Order
When a claw fails after provider/model changes, recover in this exact order:
- Fix model in each
openclaw.json(agents.defaults.model.primaryonly). - Run
bash scripts/update-oauth-token.sh ...for the target provider. - Restart services (script does this unless
--no-restart). - Verify:
bash scripts/talk.sh --listbash scripts/talk.sh --healthbash scripts/talk.sh --health --require-root-proof --json(definitive SSH/UFW proof; fails if checks are inferred)bash scripts/talk.sh --claw <name> --new --message "Reply ONLY: OK"
If talk.sh says Could not auto-discover agent ID, re-run with --agent <id> from --list.
Continuous Improvement Loop (Use skill-issue)
After every incident or rollout, run this loop to keep the bootstrap skill accurate:
- Audit current template and scripts
bash scripts/review_kit.sh --skill
- Audit each live claw
bash scripts/review_live.sh --host <ip> --service <unit> --home <openclaw-home> --user <app-user>
- Capture drift and breakages
- Record root cause + fix in
references/deployment-workflow.mdand update template assets/scripts
- Record root cause + fix in
- Patch the reusable kit, not just the live box
- Update
assets/client-kit/*, then sync any private instance snapshots in your ignoredassets/instances/<claw>/
- Update
- Re-validate
bash scripts/validate_client_kit.sh assets/client-kitbash scripts/review_kit.sh --skill
Validate Before Shipping
Run the bundled validation script against the generated kit:
VAL="$(ls ~/.codex/skills/openclaw-client-bootstrap/scripts/validate_client_kit.sh ~/.claude/skills/openclaw-client-bootstrap/scripts/validate_client_kit.sh ./scripts/validate_client_kit.sh 2>/dev/null | head -1)"
bash "$VAL" /tmp/<client>-openclaw
Reject the deployment if:
- placeholders remain in
openclaw.json - placeholder secrets remain in
.env - removed config keys detected (v2026.2.15 schema violations)
- any kit script fails shell syntax checks
Deploy Workflow
Use the generated kit's scripts in this order on the droplet:
scripts/01-bootstrap-do.shscripts/02-install-tailscale.shscripts/03-install-openclaw.shscripts/04-validate.sh- Optional for shared operator sessions:
scripts/05-setup-collab-tmux.sh
Then verify Telegram notification delivery and SPAPS/portal connectivity.
Cloud firewall requirement: remove public 22/tcp and rely on Tailnet SSH only.
Reference: references/deployment-workflow.md
Governance Rules (Do Not Relax By Default)
- OpenClaw stores read-only credentials only.
- External writes are proposed, never executed directly.
- Write execution requires human approval through the Unclawg portal, backed by SPAPS.
- Operator approvals must be logged with identity, timestamp, payload hash, and result.
Reference: references/read-only-governance.md
Deployed Instances
Two-tier tracking — non-secret config committed, secrets server-only:
assets/instances/<claw-name>/— committed source of truth for each claw'sopenclaw.json,SOUL.md,AGENTS.md,USER.md,.env.example. If the droplet root disk is lost, redeploy from here.references/deployed-instances.md— private instance registry with IPs and connection details (gitignored).envon server — real secrets, never committed
See assets/instances/README.md for the full pattern and redeploy steps.
After bootstrapping any new claw: copy its non-secret files into assets/instances/<claw-name>/ and commit.
Minimum Droplet Specs
- RAM: 2 GB minimum (gateway uses ~800MB; 1GB will OOM)
- Swap: 2 GB recommended
- Docker: Required for agent sandbox mode
- Node heap: Set
NODE_OPTIONS=--max-old-space-size=768in systemd service
Config Schema Notes (v2026.2.15+)
The kit template may need these fixes for current OpenClaw versions:
channels.pairingis not a valid channel — usechannels.telegram.groupPolicy+groupAllowFrom+groups- Telegram token key is
botToken, nottoken tools.exec.askexpects"off"|"on-miss"|"always", not an arraytools.policyMode,tools.exec.fallback,tools.exec.rulesare not recognized — do not add themtools.elevated.require/allowWhenRequestedByare not recognized; usetools.elevated.enabled+tools.elevated.allowFrom.telegram- Run
tailscale set --operator=<app_user>for Tailscale serve permissions
Updates for v2026.2.17–2026.2.18
tools.exec.safeBinsrequired — now enforced; entries must be executable names (e.g.["jq", "grep", "cut", "sort", "uniq", "head", "tail", "tr", "wc", "echo", "printf"]), not directory paths like/usr/bin. Missing or path-style entries can causeexec denied: allowlist miss.- Ask mode for allowlist security — when using
tools.exec.security: "allowlist"for autonomous reads, usetools.exec.ask: "on-miss"so non-allowlisted commands route to approvals instead of hard-denying. - Approval recipient interpolation — avoid
${env:...}inapprovals.exec.targets[*].to; set a concrete Telegram user/chat ID. - Cron webhook auth token —
SPAPS_WEBHOOK_AUTH_TOKENis now supported for authenticated webhook delivery to SPAPS. Add to.envand wire into any cron channel config. - SSRF guards on cron webhooks — v2026.2.18 blocks private RFC1918 ranges, NAT64, 6to4, and Teredo in webhook POST targets.
SPAPS_API_URLmust be a public or Tailscale-routable hostname (not a raw private IP). - Model forward compat — pin to a runtime-supported model id (for example
anthropic/claude-haiku-4-5-20251001on OpenClaw 2026.2.17). Validate from live logs before rollout. - Telegram inline button styles — approval notifications can use
primary,success,dangerbutton styles for Approve/Reject UX. - Co-located claws — use
APP_HOME,OPENCLAW_HOME, andOPENCLAW_SERVICE_NAMEwhen installing additional claws on the same droplet. - Native approval forwarding schema — current runtime supports
approvals.exec.mode: session|targets|both; avoid legacyspapsmode blocks inopenclaw.json. - Runtime placeholders —
{{MediaPath}},{{Prompt}},{{MaxChars}}are valid runtime tokens for CLI media models; do not treat these as unresolved template placeholders.
Updates for v2026.2.19–v2026.2.26
- BREAKING: SafeBins trusted directory enforcement —
tools.exec.safeBinsentries must resolve from trusted bin directories (default:/bin,/usr/binonly). Custom bin paths (e.g./home/openclaw/.openclaw/bin) require explicit opt-in viatools.exec.safeBinTrustedDirs. Without this, custom safeBin entries will fail resolution at runtime. - BREAKING:
sortandgrepremoved from default safe bins — upstream hardened defaults to removesort(file write via-o) andgrep(recursive reads). Explicitly listing them in config still works but triggersopenclaw doctorwarnings. Keep them if needed for read-only analysis, but ensuresafeBinTrustedDirscovers their parent directory. - BREAKING: Heartbeat
directPolicy— new keyagents.defaults.heartbeat.directPolicy(allow|block) replaces DM toggle. Default changed toallowin v2026.2.24. Set"block"if DM heartbeat delivery is undesired for client claws. - BREAKING: Docker container namespace join blocked —
network: "container:<id>"blocked by default. Break-glass:agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true. - BREAKING: DM allowlist enforcement tightened —
dmPolicy: "allowlist"with emptyallowFromnow silently drops all DMs (was permissive). Always populateallowFromwhen usingallowlistpolicy.openclaw doctor --fixrestores from pairing-store. - Telegram
streamModedeprecated — replace"streamMode": "partial"with"streaming": true. Legacy values auto-mapped but deprecated since v2026.2.21. - Browser SSRF policy key renamed —
browser.ssrfPolicy.allowPrivateNetworkrenamed tobrowser.ssrfPolicy.dangerouslyAllowPrivateNetwork.openclaw doctor --fixauto-migrates. - Secrets management — new top-level
secretsconfig block (secrets.providers,secrets.defaults) for env/file/exec secret sources.openclaw secrets audit/configure/apply/reloadCLI workflow available. - Plugin entries resilience —
plugins.entries.*with unknown IDs now logs warnings instead of crash-looping gateway boot. - Node exec approvals hardened — structured
commandArgvapprovals forhost=node,GIT_EXTERNAL_DIFFblocked in host env keys. - Auth profile alias normalization —
mode -> type,apiKey -> keyaliases accepted inauth-profiles.json. openai-codex-responsesaccepted in config model API schema.session.parentForkMaxTokens— default100000(0disables). Prevents oversized parent-session inheritance from bricking thread sessions.meta.lastTouchedAtaccepts numeric timestamps — coercesDate.now()values to ISO strings for forward compat.gateway.http.securityHeaders.strictTransportSecurity— optional HSTS header for direct HTTPS deployments.
Bundled Assets
The complete production template lives in:
assets/client-kit/openclaw.jsonassets/client-kit/.env.exampleassets/client-kit/SOUL.mdassets/client-kit/AGENTS.mdassets/client-kit/USER.mdassets/client-kit/checklists/*assets/client-kit/security/WRITE_GATEWAY_CONTRACT.mdassets/client-kit/security/PERMISSIONS_PLAYBOOK.mdassets/client-kit/scripts/*