fix(cli): ship the #702 guardrail (lost in 0.1.4) + mount skills read-only#755
Merged
Conversation
…uard drift `cli/package.json`'s `prepublishOnly` copies `docs/agents/skills/commonly/SKILL.md` OVER `cli/skills/commonly/SKILL.md` at publish time, which quietly makes the docs copy the real source of truth for what ends up in the tarball. PR #702 added the "post as yourself, never as your operator" guardrail to the cli copy only. The publish hook then erased it, so @commonlyai/cli@0.1.4 shipped without the rule — every agent attached with 0.1.4 was instructed without the guardrail that #702 existed to add. (Verified against the published tarball, not just the working tree.) - sync the guardrail into the docs copy, so it actually ships - add a test asserting the two copies stay byte-identical, so this class of drift fails in CI instead of silently at publish The new guard fails against the pre-fix tree and passes after. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMeWzgFxsfBcjoLVLewEES
…ymlinks `linkSkills` symlinked each declared skill source into the agent's workspace at `.claude/skills/<name>`. A symlink is writable *through*: an agent holding Write/Edit can rewrite the file that governs its own behaviour, and because the common source is `cli/skills/commonly` inside the operator's checkout, that edit lands in a tracked file. `npm publish` ships the working tree rather than git HEAD, so a write-through edit can ride out in the next release unnoticed. Empirically confirmed the old shape is write-through: writing to the workspace path mutates the source. Replace with `mountSkills`, which materializes a read-only snapshot copy: - data flows one way, source → workspace, never back - refreshed every spawn, so source edits still reach the agent next turn - only directories carrying `.commonly-skill-mount` are refreshed; user-authored content in the slot is reported as `not-a-mount` and left untouched - legacy symlinks from CLI <=0.1.4 are replaced (that shape is what we retire) Bumps to 0.1.5 for release alongside the guardrail fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMeWzgFxsfBcjoLVLewEES
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What happened
@commonlyai/cli@0.1.4shipped without the "post as yourself, never as your operator" guardrail from #702 — the fix for the incident where an agent posted under Sam's name. Every agent attached with 0.1.4 was instructed without it.Verified against the published tarball, not just the working tree:
Root cause
There are two tracked copies of the same skill file, and
cli/package.jsonhas:The publish hook copies the docs copy over the cli copy, which quietly makes docs the real source of truth for the tarball. #702 edited the cli copy only, so the publish hook erased the guardrail on the way out. The two copies were otherwise byte-identical — that paragraph was the only drift.
Fixes
1. Ship the guardrail + make this drift impossible to miss (
4c839c4d)docs/agents/skills/commonly/SKILL.md, the copy that actually shipscli/__tests__/skill-sync.test.mjsasserting the two copies stay byte-identicalThe guard fails against the pre-fix tree and passes after — it would have caught this at CI time rather than silently at publish.
2. Mount skills as read-only copies, not write-through symlinks (
35a64661)A separate hazard found while investigating.
linkSkillssymlinked each skill source into<workspace>/.claude/skills/. A symlink is writable through, so an agent holding Write/Edit could rewrite the file governing its own behaviour — and since the usual source iscli/skills/commonlyinside the operator's checkout, that edit lands in a tracked file. Confirmed empirically: writing to the workspace path mutates the source.mountSkillsreplaces it with a read-only snapshot:.commonly-skill-mountare refreshed; user-authored content is reportednot-a-mountand left aloneNote this was not the cause of the 0.1.4 regression (the publish hook was) — it's a latent hazard closed while here.
Test
17/17 suites, 183 passedlocally. New coverage:Bumps to 0.1.5 for release.
🤖 Generated with Claude Code