Skip to content

fix(cli): ship the #702 guardrail (lost in 0.1.4) + mount skills read-only#755

Merged
lilyshen0722 merged 2 commits into
mainfrom
fix/cli-skill-mount-readonly
Jul 25, 2026
Merged

fix(cli): ship the #702 guardrail (lost in 0.1.4) + mount skills read-only#755
lilyshen0722 merged 2 commits into
mainfrom
fix/cli-skill-mount-readonly

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

What happened

@commonlyai/cli@0.1.4 shipped 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:

published @commonlyai/cli@0.1.4  → guardrail present: 0
origin/main cli/skills/commonly/SKILL.md → guardrail present: 1

Root cause

There are two tracked copies of the same skill file, and cli/package.json has:

"prepublishOnly": "... copyFileSync('../docs/agents/skills/commonly/SKILL.md','skills/commonly/SKILL.md')"

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)

  • sync the guardrail into docs/agents/skills/commonly/SKILL.md, the copy that actually ships
  • add cli/__tests__/skill-sync.test.mjs asserting the two copies stay byte-identical

The 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. linkSkills symlinked 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 is cli/skills/commonly inside the operator's checkout, that edit lands in a tracked file. Confirmed empirically: writing to the workspace path mutates the source.

mountSkills replaces it with a read-only snapshot:

  • one-way data flow, source → workspace, never back
  • refreshed every spawn, so source edits still reach the agent next turn
  • only dirs carrying .commonly-skill-mount are refreshed; user-authored content is reported not-a-mount and left alone
  • legacy symlinks from CLI ≤0.1.4 are replaced

Note 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 passed locally. New coverage:

  • regression test that a write into the mount cannot reach the source
  • mounted files are read-only; re-mount refreshes from source; legacy symlink replaced; user content never clobbered
  • skill-copy sync guard (3 tests)

Bumps to 0.1.5 for release.

🤖 Generated with Claude Code

lilyshen0722 and others added 2 commits July 25, 2026 15:33
…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
@lilyshen0722
lilyshen0722 merged commit 22664a3 into main Jul 25, 2026
9 checks passed
@lilyshen0722
lilyshen0722 deleted the fix/cli-skill-mount-readonly branch July 25, 2026 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant