Releases: crisandrews/ClawCode
v1.7.6 — Orphaned reminders now self-heal (mechanical reconcile audit)
Fixes
- Skills/crons (writeback): new
auditsubcommand — a mechanical CronList-vs-registry diff that refresheslastSeenAlive, relinks reminders that survived under a new task id, flags ambiguous live duplicates as blocked instead of guessing, and persists the result for every other surface to read. Closes #32: a partially-executed reconcile could leave reminders "registered" but never firing, with no alert anywhere. - Hooks/reconcile-crons: the SessionStart envelope now verifies itself — audit first, recreate only what is actually missing, re-audit, retry once, and report the mechanical count to the user, with explicit "single unit of work" wording so an interleaved chat reply can't strand the reconcile halfway.
- Skills/heartbeat: always-on cron-health step (runs even outside active hours) — prunes fired one-shots, audits, and re-creates orphans every 30 minutes, so a long-running service session no longer waits days for the next restart to heal. Notifications are throttled.
- Skills/crons (writeback):
set-aliveno longer resurrects a stale.reconcilingmarker — the CronCreate bypass window only slides while a reconcile is making real progress. - Lib/doctor:
/agent:doctorreads the persisted audit and warns when reminders are registered but not firing (or blocked by ambiguous duplicates), with the exact keys and the fix; the doctor skill refreshes the audit before printing the card so the two can never contradict each other. - Docs/crons: corrected the two-sessions claim — the registry lock serializes writes; it never made a second session skip its reconcile.
Changes
- CI: new GitHub Actions workflow validates dependency install, jq availability, and shell-entrypoint parsing on ubuntu + macos for every push/PR.
Thanks @wrahman0 for the detailed #32 field report — the audit and orphan-count design follow those suggestions closely.
Full changelog: CHANGELOG.md.
v1.7.5 — reminders stop resurrecting after they fire; long reconciles no longer stall
Changes
- Skills/crons (writeback): new
prune-expiredsubcommand —bin/cron-from.shnow records each one-shot's target epoch (stamp line 3), the PostToolUse hook persists it into the registry astargetEpoch, and every SessionStart reconcile retires fired one-shots deterministically instead of resurrecting them scheduled a year out. Legacy date-shaped reminders (created before the field existed) are reported as suspects in the session banner for the user to verify — never auto-removed, and intentional annuals stay quiet. - Skills/channels + Lib/channel-detector: paired with claude-whatsapp 1.21.0 — guidance now says the waiting session takes over the WhatsApp lock automatically when the holder exits (full relaunch only needed on older channel versions), and the detector recognizes the new
connect_errorruntime state as a problem instead of an unknown status. - Skills/crons + Skills/import: OpenClaw
at-job imports now pass--target-epoch, so imported dated reminders are properly retired after firing.
Fixes
- Skills/crons (writeback):
set-aliverefreshes the.reconcilingmarker on every recreated entry, so a reconcile interleaved with live channel chat no longer outlives the 10-minute bypass window and gets blocked mid-flight with a misleading stale-stamp error. Everycron-pretoolrejection now also teaches the mid-reconcile recovery (re-touch the marker and retry). - Hooks/cron-posttool: ad-hoc capture no longer coerces
recurring: falsetotrue(jq//swallows boolean false) — without this, captured one-shots were invisible to the new prune and the resurrection bug would have survived its own fix. - Bin/cron-from: BSD/GNU
datedetection is value-checked (GNU first), so a file named1in the working directory can no longer misclassify the platform. - Lib/doctor: the stale-tombstone warning no longer suggests a command that doesn't prune; tombstones are deliberately kept so deletions stay resurrection-proof.
Full changelog: CHANGELOG.md.
v1.7.4 — surface locked-out WhatsApp channel
Fixed
/agent:channelsnow surfaces a locked-out WhatsApp server instead of reporting it as fine. The channel detector previously only checked thatstatus.jsonexisted; it never read the livestatus. So when a second session held the WhatsApp single-device lock (common after an in-session update/reload, or when a background/service/scheduled session was still alive),/agent:channelsshowedactive: ❓and the agent stayed silent while the user saw "typing…" on their phone but got no reply. The detector now readsstatus.json, exposes aruntimefield (status, holder PID,inboundActive, remediation), marks the channelactive: noon a problem state (idle_other_instance/logged_out/lock_error), andchannels_detect({ format: "table" })prints a⚠️ Runtime block with the fix. Thechannelsskill now leads with this warning and never suggests/whatsapp:configure resetfor a lock-ownership problem.- Rebuilt the
exec-gatebundle so its integrity header (source-sha) matches the current source. The committed bundle had drifted since 1.7.3 — the compiled code was identical, only the integrity header was stale — which tripped the tier-1 drift guard. Full suite is back to green.
Compatibility
- Pairs with claude-whatsapp 1.20.1, which writes the enriched
idle_other_instancestatus. Degrades gracefully against older claude-whatsapp that writes onlyholder. No config or tool-signature changes.
v1.7.3 — Import channel-content guard (scope provenance)
Fixes
- Skills/import: new "channel-content guard" (Step 5a) flags copied OpenClaw daily logs that contain channel-derived content (WhatsApp/Telegram JIDs,
t.me/, voice-note phrasing,wacli/baileys) and offers quarantine (to a non-indexed./import-quarantine/), skip, or import-as-local-with-acknowledgment — instead of silently landing chat summaries inmemory/*.md, where they'd be classifiedlocalprovenance and bypass channel-scope filtering even under enforce.MEMORY.mdis scanned warn-only; flagged files are recorded inIMPORT_BACKLOG.mdand surfaced in the import report. Step F also clarifies thatmemory.extraPathsis the correct provenance lane for channel history. The scope engine (lib/scope/*) is unchanged.
Full changelog: CHANGELOG.md.
v1.7.2 — Never trust a display name (anti-spoofing identity rules)
Changes
- Agents/messaging: the agent's runtime instructions and workspace templates now teach JID-based sender identity — owner trust comes from the channel's
is_ownerflag, never a display / quoted-author / contact-card name; group participants are non-owner unlessis_owneris true; and the agent must never record a "this JID is the owner" or "these two JIDs are the same person" fact from a chat name. Delivered via the runtime MCP instructions, so existing agents pick it up next session. Pairs with claude-whatsapp 1.20.0, which emits the authoritative identity fields.
Full changelog: CHANGELOG.md.
v1.7.1 — Bash-heredoc fix for first-run bootstrap + setup writes
ClawCode 1.6.0's always-on protected-paths defense refuses MCP Write to agent-config.json and channel access.json regardless of mode — by design for security, but two legitimate setup flows broke as a side effect. The first-run bootstrap wizard (every new user) couldn't write its own agent-config.json, and /whatsapp:access pair <code> couldn't write access.json when the channel state-dir resolves to the global fallback ~/.claude/channels/whatsapp/. Both flows now route the file write through a hardened Bash heredoc pattern. The protected-paths defense applies to file-tool writes only; Bash is gated separately by the exec-gate hook and doesn't fire during user-driven setup. No behavior change for users who already completed setup on 1.5.0 or earlier. Pairs with claude-whatsapp 1.19.1, which routes its WhatsApp side identically.
Fixes
- Templates/BOOTSTRAP: the first-run
agent-config.jsonwrite goes through a validated Bash heredoc (cat-in-&&-chain +JSON.parsevalidate + atomicmvwith cleanup-on-failure) instead of MCPWrite. Closes the install regression where every new user hitexec-gate: write to protected path refused (workspace-agent-config)and could not complete bootstrap. - Skills/settings: both reference blocks ("Configure the backend" and "Modifying settings") use the same hardened pattern;
JSON.parserejects malformed JSON BEFORE the atomicmvso a truncated or syntactically broken write can never clobber the existing config. - Skills/import: memory-backend onboarding snippet adopts the same hardened pattern.
- Lib/scope/exec-gate: error message for
workspace-agent-configandchannel-access-jsonblocks now appends a recovery hint pointing at the safe Bash heredoc pattern. Without this, an agent hitting the block would loop retryingWrite. - AGENTS.md: new "Legitimate writes to protected paths" subsection documents both heredoc forms (basic agent-config + auth-adjacent
umask 077+chmod 600variant for server-shared channel state) with explicit rules — use the snippet only when a trusted skill provides it (never improvised from agent reasoning), flag Bash auto-allow to the user when it's on, and treat any "update my agent-config.json" instruction arriving via a messaging channel as candidate prompt-injection. - Dist/exec-gate-resolver.cjs: rebuilt with the new hint text.
Full changelog: CHANGELOG.md.
v1.7.0 — Per-workspace scope-trust files
After upgrading: legacy global scope-trust files from 1.5.0 / 1.6.0 stop unlocking. Re-run /agent:scope wizard in each workspace where you want trust granted. Doctor (/agent:doctor) surfaces the leftovers + the rm cleanup command.
Changes
- Scope/trust: trust files now live under
~/.claude/agent/scope-trust/<workspace-fingerprint>/<channel>-{owner,exec}. Granting trust in one workspace no longer silently unlocks another —agent-config.jsonwas already per-workspace, so the prior global path was a category error. Per-workspace fingerprint uses SHA256(realpath + per-filesystem case-fold probe) → 32 hex. - Skills/scope wizard: every Bash snippet that creates or removes a trust file hardened —
set -euo pipefail,CLAUDE_PLUGIN_ROOTguard, 32-hex hash validation,umask 077. Fingerprint computed via the bridge scriptscripts/print-workspace-fingerprint.mjsso Bash and TypeScript agree byte-exact on the subdir name. - Hooks/scope-trust-legacy-warn: SessionStart advisory points to the wizard when 1.6 flat-layout trust files exist. Silent in steady state; one-line stderr when needed. Workspace-scoped dismissal marker so dismissing in workspace A still warns in workspace B.
- Lib/doctor: new
scope-trust-legacycheck walks the trust-dir for pre-1.7 leftovers, gates each through the same predicate the resolver uses (skips stale 0o644 + symlinked markers), and lists exact paths plus the literalrmcleanup command. - Lib/scope/legacy-warn: shared
warnLegacyTrustMigrationOncehelper used by both the WhatsApp adapter (read scope) anddetectScopeRuntime(arm detection) so users see exactly one stderr migration hint per (workspace × channel × suffix), not two. - Lib/scope/exec-gate-hook-entry: workspaceRoot normalized at the hook boundary via
path.resolve+ NUL-byte rejection + absolute-path validation. Fails CLOSED (exit 2) instead of through the top-level fail-open catch. - Lib/scope/exec-gate: enforce + shadow branches both surface "legacy global exec trust ignored for this workspace" when workspace-scoped exec trust is missing AND a valid 1.6 global file exists. Shadow event payload gains
legacyGlobalExecTrustIgnored?: boolean.
Fixes
- Read-scope owner unlock no longer silently degrades to guest when a 1.6 user upgrades — three surfaces (SessionStart hook, runtime warn, doctor row) make the migration cliff loud rather than silent.
- Cross-workspace isolation regression closed: 1.6 trust files acted globally despite per-workspace config.
Full changelog: CHANGELOG.md.
v1.6.0 — Execution gate (opt-in)
What this release brings
ClawCode now ships an opt-in execution gate. If you've paired the agent with a group chat (WhatsApp via claude-whatsapp), this layer prevents the agent from running destructive tools — Bash, Write, Edit, Task, and more — when the current turn was triggered by a non-owner message from that chat. Think of it as the action-side companion to the channel-scope filter shipped in 1.5.0: read scope governs what the agent sees, the execution gate governs what it can do.
The default is off, so existing users see zero change. Turn it on through /agent:scope wizard: pick a channel, a policy (denylist of destructive tools — recommended — or allowlist of safe ones), and whether to start in shadow (log only) or enforce (block).
Some hardening fires regardless of the gate's state. Plugin internals, the gate's own source files, agent-config.json, scope-trust directory, SSH/credential dirs, shell init files, LaunchAgents / systemd user units, and channel access.json are refused as Write/Edit targets even with the gate off. That closes the bootstrap-attack surface where a prompt-injected agent could otherwise modify the gate before turning it on.
A separate trust file (~/.claude/agent/scope-trust/<channel>-exec, created via Bash through the wizard) unlocks the gate for trusted machines. It's independent from the read-scope <channel>-owner trust — you can read all your channels' content from your own machine without granting non-owner group chats the power to invoke destructive tools.
Changes
- New
/agent:scopewizard steps cover the new gate (activate Y/N → denylist or allowlist → trust this machine Y/N), the trust file is created via Bash with a permission prompt, never silently. /agent:scope statusnow surfaces per-channel exec-gate state including trust file validity (distinguishes a present-but-invalid file from a valid one — the resolver and the doctor agree on the same predicate)./agent:scope test exec <senderJid> <toolName>runs a real resolver dry-run vianpx tsx -e, honoring the hard-deny pair (Bash,Task), trust file unlock, and protected paths./agent:scope disablenow resets both read scope AND exec-gate to off, and removes both trust files.- Two new doctor checks:
scope-execgate-status(per-channel info row) andscope-execgate-shadow-events(warns when recent shadow events exist, so you can review before flipping to enforce). - Hook hot path stays at ~8 ms p95 when the gate is off; armed path is ~65 ms p95 via a pre-built CJS bundle (avoids the
tsxcold start on every tool call). Bundle drift is detected by a tier1 test that recomputes the SHA over the source-file tree esbuild pulled in. BashandTaskare hard-denied when the gate fires regardless of the user's tool list. Shell command grammar is too rich to safely parse; subagent hook propagation isn't guaranteed by Claude Code. Wholesale deny is the defensible posture for both.
Documentation
PRIVACY.mdadds an Execution scope subsection covering what the gate does and explicitly what it doesn't (memory poisoning across turns, reply egress, terminal-side user actions, within-lookback envelope replay).docs/channel-scope-compat.mdexpanded "Execution scope" section.AGENTS.mdadds short guidance for the agent: when a PreToolUse block hits stderr, don't retry, don't rewrite the same intent as another blocked tool — surface the reason to the user.
Generated with Claude Code.
v1.5.0 — channel scope + WebChat sessions
What this release brings
ClawCode 1.5.0 ships a brand-new opt-in privacy layer for indexed messaging-channel content. Until now, when your agent paired with claude-whatsapp and you ran memory_search or recalled context from dreams, the agent could see snippets from every chat it had indexed — regardless of the per-chat access rules claude-whatsapp already enforced on its own tools. This release closes that gap.
Turn it on with /agent:scope wizard and the wizard walks you through three choices: which channel, what mode (observe-only shadow or actively filter with enforce), and how the agent should identify itself (owner if you want the agent to see all your chats, guest if you want it sandboxed, or auto for the conservative default). Owner mode requires an out-of-band trust file you create yourself — the agent can't grant itself elevated privileges via MCP.
Default is off everywhere. If you don't run the wizard, ClawCode behaves exactly as it did in 1.4.x.
The release also includes:
- WebChat privacy fix. Every browser tab now has its own
sessionId, so two browsers sharing the samehttp.tokencan't see each other's chat history or live agent replies.sessionIdis a privacy partition (per-tab);http.tokenremains the auth boundary. - Per-chat semantics with
claude-whatsapp1.19.0+. When both plugins are on the new releases, your agent's memory searches automatically bind to the specific WhatsApp chat that triggered the turn — mirroring upstreamhistoryScoperules byte-exact. - Security hardening. A dedicated blocklist refuses MCP-side writes to security-sensitive config keys (scope policy, voice output dir, memory paths, QMD command). Voice output paths are canonicalised and allowlisted. Skill install/remove now validates names + path containment. Live-config refuses to hot-reload privileged keys.
If you don't pair WhatsApp or don't opt in to scope, none of this changes anything for you — the existing behavior is preserved.
Changes
- Skills/scope: new
/agent:scopeskill suite —wizard,enable,disable,status,test <chatId>,audit. Interactive opt-in for per-channel scope filtering at the MCP boundary. - Lib/scope: per-channel opt-in scope tree under
config.scope.<channel>(defaultoffeverywhere). Modes:off | shadow | enforce. - Lib/scope/synthetic-indexer: per-chat synthetic chunks generated from upstream
messages.db(read-only, WAL-respecting). Replaces daily-transcript chunks for per-chat semantics. - Lib/scope/dreams: dual-lane promotion — local candidates promote to
memory/MEMORY.md; armed-channel candidates route tomemory/.scoped/<channel>/MEMORY.<encoded-chat>.mdwith PII redaction markers. - Lib/scope/envelope: cross-plugin request envelope contract.
memory_search/memory_get/memory_context/voice_transcribeaccept an optionalrequestEnvelopeTokenarg for per-chat binding when paired withclaude-whatsapp1.19.0+. - Lib/http-bridge: WebChat per-tab session model. Every
/v1/chat/send//v1/chat/history//v1/chat/streamrequest requiressessionId(UUID v4). Per-session message cap (500), pin-on-active LRU, SSE-clients-per-session cap (8). - Lib/doctor: new scope checks —
scope-status,scope-stale,scope-owner-assertion,scope-schema-drift,scope-bypasses,scope-quarantine-pending,scope-wizard-available,scope-indexer-health. - Server: defensive
chat_inbox_readgate onruntime.channels.webchat?.armed && mode === "enforce".
Fixes
- Lib/scope/runtime:
applyPreventivePromoteGuardnow resolves the live runtime instead of defaulting to no-armed — closes a path where channel chunks could leak intoMEMORY.mdmid-session. - Lib/scope/whatsapp: explicit
guest/denyconfig now beats bootstrap fail-open. Malformedaccess.jsonwith missingownerJidsfield is rejected. - Lib/voice: voice output path canonicalised (realpath + case-fold + allowlisted to
outputDir/os.tmpdir()//tmp). Refuses any path resolving under the trust-file directory. - Lib/skill-manager:
skill_installandskill_removevalidate name (slug charset, no separators/NUL, length cap, reject./..) and path containment. - Lib/live-config: hot-reload refuses to update privileged scope/voice/memory keys. Preserves prior in-memory value and surfaces a notification.
Security hardening
- New out-of-band trust file primitive
~/.claude/agent/scope-trust/<channel>-ownerfor owner unlock — the agent cannot grant itself owner privileges via MCP. agent_config(action='set')blocklist refuses scope keys, prototype-pollution segments, oversize keys, and privileged keys (voice.outputDir,memory.extraPaths,memory.qmd.commandand their ancestors/descendants).- Voice text-hash binding removed — was a wrong primitive (false positives on benign transforms, false negatives on paraphrases).
Compatibility
- Zero behavior change when
config.scope.*is absent. - Old
claude-whatsapppaired with 1.5.0: per-chat semantics fall back to the owner-only ceiling. Owner unlock via trust file unaffected. Updateclaude-whatsappto 1.19.0+ for full per-chat semantics. - New
claude-whatsapppaired with old ClawCode: silently ignores envelope token; no upstream breakage.
Full changelog: CHANGELOG.md
v1.4.14 — Windows via WSL2 documented
Changes
-
Docs/wsl2: publish
docs/wsl2.mdwalking Windows users from zero to a running agent via WSL2. Opens with a "where is your Claude Code running right now?" diagnostic (uname -atells them whether they're already inside WSL2 or stuck on native Windows), so a Windows user with an existing native Claude Code install doesn't silently install ClawCode into an environment where the Linux code paths can't run. Then PowerShellwsl --install, Ubuntu deps (nodejs npm jq git), a separate Claude Code install inside WSL2 (coexists with any native install), ClawCode plugin install, systemd user service withloginctl enable-linger, and a "what works / what doesn't" section (iMessage is the one exclusion;sayTTS auto-skips;memory.extraPathsinherits the native-Linux recursive-watch caveat). Closes the recurring "does this run on Windows?" question — nothing in the plugin needed to change architecturally because WSL2 reportsprocess.platform === "linux"and every platform-gated path already takes the Linux branch. -
Docs/service: replace the "Windows not supported" row with two rows —
Windows (via WSL2) → systemd (--user)and a separateNative Windows / BSD / other → Not supported. -
Plugin/readme: badge now advertises
macOS | Linux | Windows (WSL2)— "Windows" explicit so scanning users on Windows see their platform listed — and Prerequisites adds a line telling Windows users to run Claude Code inside WSL2 (not native PowerShell / cmd).
Fixes
- Lib/service-generator: the unsupported-OS error emitted by
/agent:service installon native Windows now points users at WSL2 +docs/wsl2.mdfirst (the path that actually works), keeping Task Scheduler as the native-only fallback. User-visible string only — no behavior change.
Full changelog: CHANGELOG.md.