scope
Skill64 starsChanged 2 months agoMITView on GitHub
Channel-scope status, opt-in, and audit. Triggers on /agent:scope, "scope status", "scope wizard", "scope enable", "scope disable", "scope audit".
42 other projects use a version of this.
Adopted by
Most recently taken up by
- sanmidable/skillstheir own version, first seen Nov 8, 2026
- lilly-builds/senior-engineer-modetheir own version, first seen Nov 8, 2026
- Kreycin/scopetheir own version, first seen Nov 8, 2026
- mblauberg/provenanttheir own version, first seen Nov 8, 2026
- 0xUrsanomics/utopia-ostheir own version, first seen Nov 1, 2026
- fynnhaupt/skillstheir own version, first seen Nov 1, 2026
Used with
Projects with it also use frames, develop and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
sanmidableSkillframes
Also in 3 projects that use this
hong9LeeSkilldevelop
빌드, 아키텍처, git 관련 작업 시 OMS MSA 공통 인프라 규칙을 적용한다. 기술 스택, 빌드 명령, 헥사고날 아키텍처 구조, git 워크플로우, AI Context 동기화 규칙을 다룬다. 코드 작성 컨벤션은 /convention, 테스트 컨벤션은 /test-guide 스킬을 참조.
Also in 6 projects that use this
minhtran3124Skillvisual
Skill graph visualizer — scans all SKILL.md files in skills/, builds a directed relationship graph from explicit `relationships` frontmatter (falling back to content inference), and generates both a Mermaid markdown summary and an interactive HTML graph at .docs/skill-graph.html. Trigger: /visual
Also in 3 projects that use this
MicrosoftSkillsync
Sync the current session branch with its upstream branch, or publish the current session branch to a remote. Use when the user asks to sync a branch, pull latest changes, rebase onto upstream, push current branch, publish branch, or set upstream.
Also in 7 projects that use this
nicolasestremSkilldocument
Create focused documentation for specific components or features
Also in 5 projects that use this
Skeln-ioSkillcopy
Direct response copywriting using proven frameworks. Brand-aware — reads ICP, voice, and tone from your brand config. Covers product descriptions, SEO meta, email, social, press angles.
Also in 3 projects that use this
Other versions42 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 42
- jsmastery-pro/skills★ 1.4ksame name
- suboss87/FDEOps★ 940same name
- AlexZio00/sovereign-skills★ 157same name
- raphaelsalaja/skills★ 62same name
- Dan-Cleary/fdestack★ 40same name
- YuriNachos/claude-scope★ 11same name
- jack4world/auditstack★ 9same name
- Kreycin/scope★ 4same name
- klmiing1229-a11y/Vague_Goal_To_Clear_Plan★ 3same name
- WhorideChicken/first-playable★ 3same name
32 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository23 other files it gives its agents.
- AGENTS.mdAGENTS.md
- templates/AGENTS.mdAGENTS.md
- templates/CLAUDE.mdCLAUDE.md
- aboutSkill
- channelsSkill
- compactSkill
- createSkill
- cronsSkill
- doctorSkill
- heartbeatSkill
- helpSkill
- importSkill
- liveSkill
- messagingSkill
- newSkill
- serviceSkill
- settingsSkill
- skill-managerSkill
- statusSkill
- updateSkill
- usageSkill
- voiceSkill
- whoamiSkill
The file
We split this skill into the rules it gives an agent
9 rules. 0 are common, 1 is distinctive, and 8 are very specific to this project.
0
Common
Most of the 39 edited versions we read keep these.
1
Distinctive
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 18
After installing claude-whatsapp + pairing it: /agent:scope status confirms the adapter sees access.json.
- Very specific: line 36
If $ARGS is empty or the subcommand is unknown, default to status.
- Very specific: line 40
Call mcp__clawcode__agent_config(action='get') and parse the scope block.
- Very specific: line 52
If scope is absent in config, say so explicitly and recommend /agent:scope wizard.
- Very specific: line 141
If foreground owner OR background system-owner was chosen, an additional Bash('mkdir -p && touch && chmod 600') runs to create the trust …
- Very specific: line 170
After applying, run status to display the new state.
- Very specific: line 184
If invoked outside an interactive REPL session, abort with: "The wizard requires an interactive REPL — use /agent:scope enable <channel> …
- Very specific: line 193
Call mcp__clawcode__agent_config(action='get') and pull scope.<channel> (default whatsapp).
- Distinctive: line 194
Use Bash to run a dry-run probe:
“Common” is measured: kept by at least 6 in 10 of the 39 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Scope
Channel-scope is the per-channel opt-in compatibility layer between ClawCode and messaging plugins that publish their own access governance (today: claude-whatsapp's historyScope). When a channel is armed (mode != off, governance resolvable), ClawCode's memory surfaces honor the upstream plugin's access rules; when a channel is mode: off (the default), behavior is identical to having no scope layer.
This is an OPTIONAL feature — see docs/channel-scope-compat.md. Enforcement covers memory_search, memory_get, memory_context, the QMD path, voice_transcribe, dream promote routing, and chat_inbox_read. Per-chat semantics flow through (a) a synthetic per-chat indexer over the upstream messages.db which produces per-chat chunks, and (b) a cross-plugin request envelope contract — claude-whatsapp embeds a requestEnvelopeToken in each inbound notification, and ClawCode resolves the token to a chat/sender binding for the current MCP call. Owner unlock (declarative identity = "owner" + out-of-band trust file) remains the always-available escape hatch; without an envelope (or with an invalid/expired one) under mode = enforce, calls fall through to guest [].
Talk to the user in the language they've been using on this turn — never default to a hard-coded language.
When to use
- After installing claude-whatsapp + pairing it:
/agent:scope statusconfirms the adapter seesaccess.json. - To turn enforcement on:
/agent:scope wizardwalks through the choices, or use the one-liner/agent:scope enable whatsapp shadow(orenforce). - To turn it off:
/agent:scope disable whatsapp. - To inspect existing channel-derived content in shared memory:
/agent:scope audit.
Steps
Step 1 — Parse the subcommand
The first word in $ARGS selects the path:
status→ show every configured channel's runtime stateenable <channel> [shadow|enforce]→ setconfig.scope.<channel>.modedisable <channel>→ set mode tooffwizard→ interactive REPL flow viaAskUserQuestiontest <chatId>→ dry-run probe against the adapter (see Step 6)audit→ re-runmcp__clawcode__agent_doctorand surface only thescope-*rows
If $ARGS is empty or the subcommand is unknown, default to status.
Step 2 — status
- Call
mcp__clawcode__agent_config(action='get')and parse thescopeblock. - For each channel under
scope, display:mode(off / shadow / enforce)identity(auto / owner / guest)background.identity(deny / system-owner)execGate.mode(off / shadow / enforce),execGate.policy(denylist / allowlist), andexecGate.toolscount (or "defaults" when omitted)- WhatsApp only:
accessJsonPath,cwdExactMatchOnly
- Surface trust file presence (read scope + exec):
<channel>-ownerexists? (yes/no) — gates read scope owner unlock<channel>-execexists? (yes/no) — gates execGate "trust this machine" path
- Then call
mcp__clawcode__agent_doctor(action='check')and surface the scope rows (scope-pre-enforce-audit,scope-bypasses,scope-quarantine-pending,scope-execgate-status,scope-execgate-shadow-events).
If scope is absent in config, say so explicitly and recommend /agent:scope wizard.
Step 3 — enable <channel> [mode]
All scope-tree writes are refused by mcp__clawcode__agent_config(action='set') (any key starting with scope). The agent cannot silently elevate or relax the policy; every scope key goes through Bash, which surfaces a permission prompt to the user.
For enable <channel> <mode> the single Bash call covers ALL scope.<channel> keys. Substitute BOTH <channel> (validated against the shipped enum {whatsapp, telegram, discord, imessage, webchat}) AND <mode> ('shadow' or 'enforce'; default 'shadow' when omitted) from $ARGS. Refuse any other literal for either parameter — only those values are valid for enable. cwdExactMatchOnly preserves the prior value if the user had it true:
Bash('node -e "const fs=require(\"fs\"),p=\"agent-config.json\";const c=fs.existsSync(p)?JSON.parse(fs.readFileSync(p,\"utf-8\")):{};c.scope=c.scope||{};const ch=\"<channel>\";const cur=c.scope[ch]||{};c.scope[ch]=Object.assign({},cur,{mode:\"<mode>\",identity:\"auto\",accessJsonPath:cur.accessJsonPath||\"auto\",cwdExactMatchOnly:cur.cwdExactMatchOnly===true,background:Object.assign({},cur.background,{identity:\"deny\"})});fs.writeFileSync(p,JSON.stringify(c,null,2));console.log(\"wrote\",p);"')The Bash call surfaces a permission prompt to the user — that's intentional. Default mode when omitted: shadow. Confirm by re-running status.
Tell the user: "Run /mcp reconnect clawcode for changes to take effect."
Step 4 — disable <channel>
First validate <channel> is one of the shipped channel names (whatsapp, telegram, discord, imessage, webchat). Reject any other value with an error message — this defends against future channel IDs that might contain shell metacharacters. Currently shipped names are alphanumeric and safe.
scope.<channel>.mode AND scope.<channel>.execGate.mode are both on the security-sensitive blocklist, so this also goes through Bash. A disable resets BOTH read scope AND execGate to off (the user expected "turn the channel off" — they don't expect read scope to flip but exec scope to remain active):
Bash('node -e "const fs=require(\"fs\"),p=\"agent-config.json\";const c=fs.existsSync(p)?JSON.parse(fs.readFileSync(p,\"utf-8\")):{};c.scope=c.scope||{};const cur=c.scope.<channel>||{};c.scope.<channel>=Object.assign({},cur,{mode:\"off\",execGate:{mode:\"off\"}});fs.writeFileSync(p,JSON.stringify(c,null,2));console.log(\"disabled\");"')Also remove BOTH trust files (read-scope owner trust AND exec trust) for THIS workspace. As of 1.7.0 the trust files live under a per-workspace fingerprint subdir; the fingerprint is computed by the bridge script scripts/print-workspace-fingerprint.mjs so Bash and TS agree byte-exact:
Bash('set -euo pipefail; [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] || { echo "CLAUDE_PLUGIN_ROOT unset"; exit 1; }; TS_HASH=$(node "$CLAUDE_PLUGIN_ROOT/node_modules/tsx/dist/cli.mjs" "$CLAUDE_PLUGIN_ROOT/scripts/print-workspace-fingerprint.mjs" "$PWD"); [[ "$TS_HASH" =~ ^[0-9a-f]{32}$ ]] || { echo "bad TS_HASH=$TS_HASH"; exit 1; }; rm -f -- "$HOME/.claude/agent/scope-trust/$TS_HASH/<channel>-owner" "$HOME/.claude/agent/scope-trust/$TS_HASH/<channel>-exec"')so both unlocks are dropped along with the mode flip. Both Bash calls surface a permission prompt — intentional: turning scope off and dropping trust are user-visible state changes. Cross-workspace isolation: this only removes THIS workspace's trust; other workspaces are untouched.
Step 5 — wizard (interactive)
Load AskUserQuestion via ToolSearch(query='select:AskUserQuestion') first.
Then walk the user through the choices, ONE question per AskUserQuestion call. Sample flow (substitute the user's language):
- Which channel?
- "WhatsApp (claude-whatsapp paired)" →
whatsapp - "Cancel" → exit, no changes
(Skip channels that are not installed/paired — only offer those whose
agent_doctorreports installed+authenticated.)
- "WhatsApp (claude-whatsapp paired)" →
- Mode?
- "Shadow — collect stats but don't filter (recommended for first week)"
- "Enforce — filter denied chunks immediately"
- "Cancel" → exit
- ¿Sos el owner de este WhatsApp? (owner-unlock primitive — two-factor:
identity = "owner"config + out-of-band trust file)- "Sí, soy el owner — quiero que ClawCode pueda buscar en mis chats sin restricción" →
identity: ownerAND step 6 will create a per-workspace trust file (as of 1.7.0) under~/.claude/agent/scope-trust/<workspace-fingerprint>/whatsapp-ownervia Bash. Both writes are required: config alone (which the agent can write) does NOT unlock — the trust file (a separateBashcall the user approves) is what makes the unlock real. The trust is scoped to THIS workspace; granting in workspace A does NOT unlock workspace B. This closes the prompt-injection escalation surface where an agent could otherwise write a config and simulate being the owner. - "No / no estoy seguro — mantener el techo conservador" →
identity: auto(default; user still has to setWHATSAPP_OWNER_BYPASS=1env if they want unlock, or pick "Sí" later) - "Soy un guest explícito — denegar siempre canales WA" →
identity: guest(NO trust file needed — guest is a deny posture, no escalation surface) - "Cancel" → exit
- "Sí, soy el owner — quiero que ClawCode pueda buscar en mis chats sin restricción" →
- Background lane (dreams / indexer)?
- "Deny — never read scoped chunks in the dream lane (default, safest)" →
background.identity: deny(NO trust file) - "System-owner — let the dream lane read as if owner (only if you control 100% of the channel and want full dream coverage)" →
background.identity: system-ownerAND step 6 will create the trust file just like the foregroundownercase. Backgroundsystem-ownerwithout trust file silently degrades to deny inresolveAllowed, so the wizard MUST touch the file or the user gets surprising "no dream coverage" behavior. - "Cancel" → exit
- "Deny — never read scoped chunks in the dream lane (default, safest)" →
4a. ¿Activar gate de ejecución? (execution gate — separate from read scope; blocks destructive tools when the current turn was triggered by a non-owner inbound)
- "Sí, recomendado — bloquear Bash y writes destructivos si un mensaje de un no-owner los dispara" →
execGate.mode: shadowinitially (will collect would-block events for review) - "Sí, modo enforce desde el principio (avanzado)" →
execGate.mode: enforce - "No, dejar abierto (el agente puede ejecutar lo que sea aunque venga de un grupo)" →
execGate.mode: off - "Cancel" → exit
4b. ¿Qué política? (only if execGate mode is shadow/enforce)
- "Denylist (recomendado) — bloquear solo tools destructivos (Bash, Write, Edit, MultiEdit, NotebookEdit, agent_config, skill_install/remove, dream)" →
execGate.policy: denylist,execGate.toolsomitted (defaults applied) - "Allowlist (estricto) — solo permitir tools de lectura/mensajería (Read, Grep, Glob, TodoWrite, WebFetch, WebSearch, reply, react, memory_search/get/context, voice_speak/transcribe)" →
execGate.policy: allowlist,execGate.toolsomitted (defaults applied) - "Cancel" → exit
4c. ¿Confiar este equipo para ejecutar tools cuando un no-owner mensaje vino del canal armado? (only if execGate mode is shadow/enforce — this creates the <channel>-exec trust file, separate from the <channel>-owner read-scope trust file)
- "Sí, soy el owner — confío en que este equipo puede ejecutar tools incluso cuando un mensaje de otro entró en el window de 60s" → step 6 will create a per-workspace exec trust file (as of 1.7.0) under
~/.claude/agent/scope-trust/<workspace-fingerprint>/<channel>-execvia Bash. The exec-trust file is SEPARATE from the read-scope owner trust file (<channel>-owner) — having one does NOT imply the other. This is intentional: a user can read their own chats without granting the agent the right to run shell commands from non-owner-triggered turns. The trust is scoped to THIS workspace; if you have execGate enforce in multiple workspaces, you must opt-in to exec-trust in each one independently. - "No, dejar el gate firmado — bloquear cualquier no-owner-inducido tool destructivo hasta que cree el archivo manualmente" → no exec trust file. Useful when the user wants to observe shadow events first before deciding.
- "Cancel" → exit
Confirmation preview: show a summary of the writes that will happen, AND a count of currently-visible channel chunks that would be filtered out under the chosen mode.
To compute the count, call:
Bash('node -e "const m=require(\"./lib/memory-db.ts\");const db=new m.MemoryDB(process.cwd(),[],{ headless:true,quietBoot:true});const n=db.countSyntheticChunksForChannel(\"whatsapp\");db.close();console.log(\"PREVIEW_COUNT=\"+n);"')Show the user: "Under the chosen mode, approximately N chunks from this channel would be filtered for non-owner queries (owner mode sees them all)."
This is a preview — exact filtering depends on per-chat allowlists and is computed at query time.
- The scope-policy writes go through
Bash(anode -eone-liner that updatesagent-config.json) becauseagent_config(action='set')refuses scope keys by design. The user gets one Bash permission prompt for the JSON edit. - If foreground
ownerOR backgroundsystem-ownerwas chosen, an additionalBash('mkdir -p && touch && chmod 600')runs to create the trust file. Second permission prompt. - Explain in plain language: "El trust file es la prueba 'out-of-band' de que vos (no el agente vía MCP) declaraste ownership. Sin el archivo el unlock no aplica, aunque la config diga
identity: ownerobackground.identity: system-owner. Las dos confirmaciones (config + trust file) son intencionales: cierran el agujero donde un agente prompt-inyectado podía escribir la config y simular ser owner." - Caveat to surface explicitly: "Si tenés Bash en auto-allow para esta sesión, el trust file se crea sin prompt — la protección depende de que aprobés cada Bash interactivamente. Si dudás, salí del wizard y corré los comandos a mano."
- Confirm via
AskUserQuestion:- "Apply" → run the
Bashconfig edit + (conditionally) the trust-file touch + close withstatus - "Cancel" → exit, no changes
- "Apply" → run the
- The scope-policy writes go through
- Apply: execute the calls in this order:
Single consolidated config write through Bash (because
agent_config(action='set')refuses scope.* keys). Substitute<channel>,<mode>,<identity>,<bg>,<egMode>,<egPolicy>from the wizard answers:Bash('node -e "const fs=require(\"fs\"),p=\"agent-config.json\";const c=fs.existsSync(p)?JSON.parse(fs.readFileSync(p,\"utf-8\")):{};c.scope=c.scope||{};const ch=\"<channel>\";const cur=c.scope[ch]||{};const bg=Object.assign({},cur.background,{identity:\"<bg>\"});const next=Object.assign({},cur,{mode:\"<mode>\",identity:\"<identity>\",accessJsonPath:cur.accessJsonPath||\"auto\",cwdExactMatchOnly:cur.cwdExactMatchOnly===true,background:bg});const eg=\"<egMode>\";next.execGate=eg===\"off\"?{mode:\"off\"}:{mode:eg,policy:\"<egPolicy>\"};c.scope[ch]=next;fs.writeFileSync(p,JSON.stringify(c,null,2));console.log(\"wrote\",p);"')Notes:
cwdExactMatchOnlypreserves the prior value if the user had ittrue— never silently flipped to false.execGate.toolsis intentionally OMITTED so the resolver applies defaults (DEFAULT_DENYLIST_TOOLS/DEFAULT_ALLOWLIST_TOOLSfromlib/scope/exec-gate.ts). If the user picked custom tools in a future wizard branch, setnext.execGate.tools = ["…"]accordingly.execGate.mode = "off"is ALWAYS written so/agent:scope statussurfaces the explicit "currently off" state and a later wizard run can detect intent.
If
identity = "owner"ORbackground.identity = "system-owner", create the per-workspace owner trust file. As of 1.7.0 trust files live under a fingerprint subdir keyed byrealpath(workspaceRoot)so trust in workspace A does NOT unlock workspace B. The bridge script computes the same hex the runtime uses — no Bash-side crypto reimplementation:Bash('set -euo pipefail; [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] || { echo "CLAUDE_PLUGIN_ROOT unset"; exit 1; }; TS_HASH=$(node "$CLAUDE_PLUGIN_ROOT/node_modules/tsx/dist/cli.mjs" "$CLAUDE_PLUGIN_ROOT/scripts/print-workspace-fingerprint.mjs" "$PWD"); [[ "$TS_HASH" =~ ^[0-9a-f]{32}$ ]] || { echo "bad TS_HASH=$TS_HASH"; exit 1; }; umask 077; DIR="$HOME/.claude/agent/scope-trust/$TS_HASH"; mkdir -p -- "$DIR"; chmod 700 -- "$DIR"; touch -- "$DIR/<channel>-owner"; chmod 600 -- "$DIR/<channel>-owner"; echo "trust file created: $DIR/<channel>-owner"')If user opted to trust the machine for execGate (step 4c "Sí"), create the per-workspace exec trust file. SEPARATE trust file from the read-scope owner trust; agent cannot create either via MCP — both go through Bash + user permission prompt. The doctor row
scope-execgate-statusreflects trust validity (mode/uid/symlink checks), not just file presence:Bash('set -euo pipefail; [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] || { echo "CLAUDE_PLUGIN_ROOT unset"; exit 1; }; TS_HASH=$(node "$CLAUDE_PLUGIN_ROOT/node_modules/tsx/dist/cli.mjs" "$CLAUDE_PLUGIN_ROOT/scripts/print-workspace-fingerprint.mjs" "$PWD"); [[ "$TS_HASH" =~ ^[0-9a-f]{32}$ ]] || { echo "bad TS_HASH=$TS_HASH"; exit 1; }; umask 077; DIR="$HOME/.claude/agent/scope-trust/$TS_HASH"; mkdir -p -- "$DIR"; chmod 700 -- "$DIR"; touch -- "$DIR/<channel>-exec"; chmod 600 -- "$DIR/<channel>-exec"; echo "trust file created: $DIR/<channel>-exec"')
After applying, run
statusto display the new state. Then ALSO surface the first-run banner to the user once, in their language:Scope active. MCP-level filtering covers
memory_search,memory_get,memory_context,voice_transcribe, dream promote, andchat_inbox_read. It does NOT cover nativeRead,Grep, or direct SQLite reads over channel log files — those bypass scope by design (MCP scope ≠ filesystem sandbox). If you want hard isolation, that lives at the OS / filesystem-permissions layer.Then also create a once-only marker so future wizard runs in this workspace don't repeat the banner:
Bash('mkdir -p ~/.claude/agent && touch ~/.claude/agent/.scope-banner-shown')Skip the banner emission if
~/.claude/agent/.scope-banner-shownalready exists — the user has already seen it.Finally remind the user to
/mcp reconnect clawcodefor changes to take effect.
If invoked outside an interactive REPL session, abort with: "The wizard requires an interactive REPL — use /agent:scope enable <channel> [shadow|enforce] instead."
Step 6 — test <chatId> (read-scope dry-run) and test exec <senderJid> <toolName> (execGate dry-run)
Show what a given chat-id (or sender+tool combo) would resolve to under the current scope config — read-only, no writes.
Read-scope dry-run (test <chatId>):
- Parse
<chatId>from$ARGS. If missing, prompt the user for one. - Call
mcp__clawcode__agent_config(action='get')and pullscope.<channel>(defaultwhatsapp). Use Bash to run a dry-run probe:
Bash('node -e "const r=require(\"./lib/scope/runtime.ts\");const c=require(\"./lib/config.ts\");const ctx=require(\"./lib/scope/context.ts\");const cfg=c.loadConfig(process.cwd());const rt=r.detectScopeRuntime(cfg,process.cwd());const adapter=r.getScopeAdapter(\"whatsapp\");if(!adapter){console.log(JSON.stringify({error:\"adapter not armed\",mode:cfg.scope?.whatsapp?.mode}));process.exit(0);}const c1=ctx.makeForegroundContext(\"dry-run\");const allowed=adapter.allowedChatIds(c1);const chatIdToTest=process.argv[1]||\"unknown\";const visible=allowed===null||allowed.includes(chatIdToTest);console.log(JSON.stringify({chatId:chatIdToTest,mode:rt.channels.whatsapp?.mode,armed:rt.channels.whatsapp?.armed,allowedShape:allowed===null?\"unrestricted (owner)\":allowed.length===0?\"deny-all (guest)\":\"partial (\"+allowed.length+\" chats)\",visible}));" "<chatId>"')- Surface the JSON output to the user in their language. Translate
visible: trueto "this chat WOULD be visible to the agent under the current scope" andvisible: falseto "this chat would be hidden". Add a line: "This is a dry-run — no config or memory state changed."
ExecGate dry-run (test exec <senderJid> <toolName>):
Show what the execution gate would decide for a given sender + tool combo. Useful for verifying allowlist/denylist semantics + hard-deny (Bash/Task) + trust file unlock before flipping shadow → enforce.
- Parse
<senderJid> <toolName>from$ARGS. If missing, prompt the user for both. Invoke the real pure-function resolver (
lib/scope/exec-gate.ts:resolve) with an injected envelope reader that returns a synthetic non-owner envelope. The resolver honors the actual trust-file check, hard-deny set (Bash,Task), and protected-paths logic.IMPORTANT: this snippet calls into
.tsfiles directly so it MUST run under tsx, but invoking vianpx tsxfrom the user's workspace cwd is unreliable (npx searches the localnode_modules/.binfirst and fetches from the registry on miss). Use the plugin-local tsx CLI explicitly vianode "$CLAUDE_PLUGIN_ROOT/node_modules/tsx/dist/cli.mjs":Bash('node "$CLAUDE_PLUGIN_ROOT/node_modules/tsx/dist/cli.mjs" -e "
const eg = require("./lib/scope/exec-gate.ts"); const c = require("./lib/config.ts"); const r = require("./lib/scope/runtime.ts"); const wa = require("./lib/scope/whatsapp.ts"); const path = require("path");
const cfg = c.loadConfig(process.cwd()); const senderJid = process.argv[1]; const toolName = process.argv[2]; const channel = "whatsapp";
const execGate = eg.execGateConfigForChannel(cfg.scope, channel); const channelDir = r.resolveWhatsappChannelDir(cfg, process.cwd()) || ""; const ownerJids = []; if (channelDir) { try { const access = wa.loadAccess(path.join(channelDir, "access.json"), new Map()); if (access.resolvable && access.access) ownerJids.push(...(access.access.ownerJids || [])); } catch {} }
// Inject a fake envelope reader so the resolver "sees" the supplied senderId // as an in-window non-owner inbound. recordShadow is a no-op for dry-run. const fakeReader = { load: (cd, token, now) => ({ version: 1, token, chatId: senderJid, senderId: senderJid, ts: now, expiresAt: now + 60000 }) }; const fakeFs = { readdirSync: () => ["dryrun.json"], statSync: () => ({ isFile: () => true, mtimeMs: Date.now() }) };
const decision = eg.resolve({ toolName, toolInput: toolName === "Bash" ? { command: "dryrun" } : {}, pluginRoot: process.cwd(), workspaceRoot: process.cwd(), memoryDir: path.join(process.cwd(), "memory"), armed: [{ channel, channelDir: channelDir || "/tmp/dryrun", ownerJids, execGate }], reader: fakeReader, fsImpl: fakeFs, effects: { recordShadow: () => {} } });
console.log(JSON.stringify({
senderJid, toolName, channel,
execGateMode: execGate.mode, policy: execGate.policy,
ownerJidsKnown: ownerJids.length,
isOwner: ownerJids.includes(senderJid),
decision: decision.decision,
reason: "reason" in decision ? decision.reason : null
}, null, 2));
" "<senderJid>" "<toolName>"')
3. Surface the JSON output. Translate `decision` to plain language (`allow` / `block` / `shadow`). Note: hard-deny tools (`Bash`, `Task`) block regardless of policy. Trust file `<channel>-exec` (if present + valid uid/mode) unlocks any non-owner-induced call. Add: "This is a dry-run — the resolver pure function was invoked but the hook subprocess was NOT spawned. To verify end-to-end including stderr surfacing, send a real test message from `<senderJid>` and observe the agent's response."
### Step 7 — `audit`
Pass-through to `mcp__clawcode__agent_doctor(action='check')`. Filter the doctor card to only the `scope-*` rows and surface them. Reference `docs/channel-scope-compat.md` for what each row means.
## Limitations
- **Owner unlock for WhatsApp**: set `scope.whatsapp.identity = "owner"` via wizard for declarative per-workspace unlock. The wizard also creates the out-of-band trust file `~/.claude/agent/scope-trust/<workspace-fingerprint>/whatsapp-owner` (Bash-prompted; per-workspace as of 1.7.0). Both are required: config alone does NOT unlock. `WHATSAPP_OWNER_BYPASS=1` env is an alternative escape-hatch. Without any of these, foreground calls hit the owner-only ceiling. Trust granted in one workspace does NOT unlock another workspace.
- **Per-chat binding for non-owner senders**: claude-whatsapp publishes a per-inbound `requestEnvelopeToken` in the MCP notification meta. The agent forwards the token to ClawCode memory tools, ClawCode validates the envelope file under `<channel-dir>/.request-envelopes/<token>.json` and emits a per-chat allowlist mirroring upstream `claude-whatsapp/scope.ts:scopedAllowedChats` byte-exact.
- **Residual risks (mirror of `docs/scope-envelope-contract.md` threat model, both kept in sync)**:
- **Same-uid filesystem forge** (architectural, out of scope): any code running as your user can plant or tamper with channel-state files including the request envelope. The uid match check in the reader rules out cross-user tampering but not same-user adversaries. Scope is a privacy/safety layer between MCP tool calls and the agent, NOT a defense against OS-level adversaries already running as you.
- **Token confusion across concurrent inbounds**: if two inbounds arrive within the 60s TTL, the agent holds two valid tokens. A prompt injection in inbound A could induce the agent to forward inbound B's token (leaking B's scope). Bounded by recent-inbound set + TTL; not closeable without per-tool-call authority binding.
- **Within-TTL replay**: same token re-used within 60s by the same agent (required for multi-tool flows like `memory_search` → `memory_get`).
- **Reply-egress taint**: once a snippet reaches the agent, voice/dream output is not taint-tracked.
- **MCP-level filtering ≠ filesystem sandbox**: native `Read`/`Grep`/SQLite over channel logs always bypass scope by design. If hard isolation is required, that lives at the OS layer.
## Reference
- `docs/channel-scope-compat.md` — full design doc + roadmap
- `docs/doctor.md` — `scope-pre-enforce-audit`, `scope-bypasses`, `scope-quarantine-pending`
- Plan: `~/.claude/plans/como-ves-poder-tener-agile-dove.md`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.

