spawn-agent-in-tenant
Skill2 starsChanged 24 days agoMITView on GitHub
Stand up a NEW agent (a Claude-Code Telegram bot) on an EXISTING tenant (host/box), correctly, from the start. An agent = its own CC session + own config dir + own Claude account + own bot token + own launcher, isolated from the orchestrator and the other agents on the same host. The host itself is the "tenant"; to prepare a brand-new host use spawn-tenant. Encodes every gotcha learned the hard way (PATH/bun ENOENT, concurrent- refresh OAuth race, bypassPermissions accept-gate, resume-prompt wall, iatrogenic watchdog). Use when the user wants a new bot/agent, or to repair/rebuild an existing one to spec.
- Reads credentials
In this repository57 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- llms.txtllms.txt
- advisorSkill
- agent-dispatchSkill
- arenaSkill
- audit-pypi-model-packageSkill
- branching-workflowSkill
- code-reviewSkill
- criticSkill
- cron-child-oauth-auditSkill
- daily-forestSkill
- deep-researchSkill
- diagnose-cron-timeoutSkill
- dreamingSkill
- eod-summarySkill
- filingSkill
- forest-synthesisSkill
- goalsSkill
- graph-hygieneSkill
- ideaSkill
- inline-tagsSkill
- intel-analyzerSkill
- longformSkill
- loop-experimentSkill
- loop-librarySkill
- meta-memory-reviewSkill
- office-hoursSkill
- open-loopsSkill
- plan-ceo-reviewSkill
- plan-design-reviewSkill
- prdSkill
- progressive-disclosureSkill
- reality-feedback-createSkill
- reality-review-weeklySkill
- recallSkill
- reconcile-extractionSkill
- redactSkill
- repo-auditSkill
- research-digestSkill
- review-gateSkill
- run-long-job-detachedSkill
- saveSkill
- scopeSkill
- self-auditSkill
- shadow-test-local-classifierSkill
- signal-scorerSkill
- skill-auditSkill
- skill-chainingSkill
- spawn-tenantSkill
- split-log-rows-by-session-kindSkill
- subagent-delegationSkill
- telegram-dump-routerSkill
- telegram-reply-toolSkill
- verify-stackSkill
- wave-planSkill
- weekly-retroSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 39
Never curl-pipe-bash. Node = official nodejs.org tarball, sha256-verified.
- Very specific: line 58
All current tenants launch identically: claude --continue --name <tenant>-main --channels plugin:telegram@claude-plugins-official inside a …
- Distinctive: line 80
NO aggressive preventive recycling (no "restart every Nh").
- Distinctive: line 81
Prefer dead-plugin recovery + drift recovery only.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
spawn-agent-in-tenant
An agent is a self-contained Claude-Code-as-Telegram-bot: its own CLAUDE_CONFIG_DIR,
its own Claude OAuth account, its own BotFather token + @handle, its own launcher, and
(for privacy-isolated agents) its own Linux user. The orchestrator, tenant-a, tenant-b and
tenant-c are agents. The tenant is the HOST/box they run on (e.g. a WSL machine); many
agents per tenant. This skill adds ONE agent to an existing tenant. To prepare a brand-new
host, use spawn-tenant.
Terminology note: some steps below say "tenant" where they mean the AGENT's own per-bot artifact (its config dir, .env, Linux user). Read those as "the agent's". The only true tenant is the host. (Legacy wording from when a single bot was called a "tenant".)
The golden rule: a new agent must look like the agents that already run for days without dropping (e.g. a reference agent with 7d+ uptime). Diff against them, don't invent. Before declaring done, the new agent's launcher + settings + uptime behaviour should match a known- stable peer. If it differs, the difference is a bug.
0. Security-first gate (NON-NEGOTIABLE, do this first)
python3 scripts/security/check_standdown.py <target>before any install / mcp-add / settings edit.- Never curl-pipe-bash. Node = official nodejs.org tarball, sha256-verified. claude-code = official
@anthropic-ai/claude-codenpm (CC is 7-day-rule exempt; first-party only). - Bot token passed at RUNTIME via env var, written only to the tenant's
.env(chmod 600, tenant-owned), NEVER echoed, NEVER committed. - This is a CONFIRM-gate action (new infra). Auto-invoke
/scope+ helicopter 3-ripple before executing.
1. Decide the isolation level
| level | when | how |
| OS-user isolation (tenant-b, tenant-c) | external/privacy boundary (an external demo, a separate business) | separate Linux user + jailed 0700 home + tenant-local node/claude. Use a host/user provisioner script as the template. |
| same-user, own config dir (tenant-a) | internal-side bot, no privacy wall | same <user>, own CLAUDE_CONFIG_DIR=<proj>/.cc-config (chmod 700). |
/home/<user> must be 0750/0700 so OS-isolated tenants cannot traverse it (a symlink does NOT bypass dir-traversal perms - give the tenant its OWN node/claude inside its jail).
2. Prerequisites (gather before building)
- BotFather token for the new bot (@handle).
- A DEDICATED Claude account for the tenant. Each tenant does its OWN fresh
/login. Account identity was proven NOT to be the cause of drops (a stable OS-isolated agent can share an account with another agent and stay stable) - so a dedicated account is for clean separation, not a churn fix. Do NOT put a churny tenant on a human's interactively-used account if avoidable, and NEVER share one credentials.json across two running instances. - bun must be installed and on PATH for the tenant (the telegram plugin spawns bare
bun; see gotcha #1). - Allowed Telegram user IDs (operator + anyone permitted). Default deny everyone else.
3. The launcher (run.sh) - copy the WORKING pattern
All current tenants launch identically:
claude --continue --name <tenant>-main --channels plugin:telegram@claude-plugins-official
inside a tmux session. The launcher MUST do, in order:
export CLAUDE_CONFIG_DIR=<dir>(into the tmux shell, before claude).export PATH="$HOME/.bun/bin:$HOME/.local/bin:$PATH"BEFORE launching claude - gotcha #1, the single most important line. Working launchers have it; the bug that cost a multi-day saga was a launcher missing it.--channels plugin:telegram@claude-plugins-official(REQUIRED for inbound DMs; without it the bot can send but not receive).- Single-instance enforcement: sweep all procs tagged with this tenant's
CLAUDE_CONFIG_DIR(TERM then KILL) + aflock -nmutex around the launch, so two launches can never interleave. Concurrent instances sharing one rotating refresh-token = the OAuth race = 401. - Resume-prompt handling:
--continueon a large/old session shows an interactive "Resume from summary?" prompt that blocks headless boot. Mirror a working agent: a backgroundedsleep 12; tmux send-keys "" Enterauto-Enter, plus a stale-session guard (drop--continueif the newest session jsonl is too old/large).
Reference launcher: a working agent's run.sh (one that has the PATH fix). OS-isolation half: a host/user provisioner script.
4. Permissions (settings.local.json)
- Mode = acceptEdits (NOT bypassPermissions - gotcha #3: bypass has a startup accept-gate that defaults to "exit" and is not persisted, so it kills headless boot).
- allow-list (broaden to kill prompts):
Read(**),Bash(*),mcp__plugin_telegram_telegram__*, scopedWriteto the tenant's own dirs. - deny-belt (the real guardrail, deny always wins even in bypass):
curl,wget,rm,sudo,crontab,systemctl,tmux,kill,pkill, Write to OTHER tenants' dirs /.agent-daemon/.claude, Read.env/.ssh/ wallet,claude_ai_*connectors. - Edit settings via python
json.load/dump(not manual, avoid fail-open). Back up first.
5. Optional watchdog - GENTLE only (gotcha #4: iatrogenic churn)
Reference agents run for days with NO watchdog. If you add one (boot-start + rare-death recovery):
- It MUST respawn via a
run.shthat exports PATH (else every cron-respawn = bun ENOENT = dead-plugin loop = the 401 saga). - STOP on a 401 / "Please run /login" - alert + halt, never restart-loop into an auth failure (restart cannot fix auth and each restart feeds the refresh race).
- NO aggressive preventive recycling (no "restart every Nh"). The "silent drop" such recycles chase is usually the PATH bug, not real.
- Prefer dead-plugin recovery + drift recovery only. Cron runs with minimal PATH - that is exactly why #3 PATH export matters.
6. OAuth login (interactive, user-action)
tmux attach -t <tenant>-cc (or sudo -u <user> -i then claude) → /login → pick the dedicated account → complete browser/device flow → detach. Cannot be done headlessly.
7. Verification checklist (must all pass before "done")
- exactly ONE tenant claude session + ONE bun poller (
pgrep/ps, match byCLAUDE_CONFIG_DIR). bunresolves from the tenant's launch PATH (env -i PATH=/usr/bin:/bin HOME=$HOME bash -c 'export PATH="$HOME/.bun/bin:$PATH"; command -v bun').- auth token valid (
expiresAtin.credentials.json, identity-only, never print the token). - deny-belt intact (curl/rm/sudo/tmux/kill + Read .env/.ssh + claude_ai_* all denied).
- launcher has the PATH export line BEFORE the claude launch.
- smoke-test: DM the bot from an allowed account → it answers; DM from a non-allowed account → rejected.
- uptime behaviour matches a stable peer (launch once, runs; not recycling every few minutes).
Anti-patterns (the saga, so nobody repeats it)
- ❌ Launcher missing the
.bun/binPATH export → cron-respawnbunENOENT → poller dies → watchdog loop → refresh-token race → 401. THE bug. - ❌ Aggressive watchdog (kill-sweep + relaunch every 2 min, "restart every 4h") → self-inflicted churn that races the OAuth token. The recovery mechanism becomes the failure source.
- ❌
--permission-mode bypassPermissionsfor a headless bot → startup accept-gate kills it. Use acceptEdits + allow-list. - ❌ Two instances sharing one
.credentials.json→ rotating refresh-token race → both forced to/login. - ❌ Blaming the OAuth account / API-key escalation before diffing the launcher against a working tenant. Diff against a working peer first.
References
- a host/user provisioner script (OS-isolation provisioner, write your own)
- a working agent's PATH-fixed
run.sh(the launcher pattern) - a multi-bot dossier (full tenant history + the PATH root cause)
- diff against a working peer first (the meta-lesson)
docs/agent-protocols.md(cross-tenant handoff)
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

