readonly
Skill2 starsChanged 12 days agoMITView on GitHub
Enter or leave a structural READ-ONLY session mode for audit/research/census work. Sets the marker the pretooluse-readonly hook enforces, which then DENIES the file-editing tools (Edit/Write/MultiEdit/NotebookEdit) until cleared. Shell writes via Bash are outside the matcher. Operator-invoked.
2 other projects use a version of this.
Adopted by
Most recently taken up by
- yuri-xyz/skillstheir own version, first seen Nov 8, 2026
- CheckPickerUpper/skillstheir own version, first seen Oct 25, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- CheckPickerUpper/skills★ 9same name
- yuri-xyz/skills★ 2same name
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 repository29 other files it gives its agents.
- AGENTS.mdAGENTS.md
- apply-review-findingsSkill
- author-workflow-fanoutSkill
- chain-launcherSkill
- delegateSkill
- design-consultationSkill
- design-fail-closed-gateSkill
- document-releaseSkill
- gistSkill
- goal-promptSkill
- grokbotSkill
- incident-as-codeSkill
- investigateSkill
- learn-captureSkill
- office-hoursSkill
- orion-deep-researchSkill
- oss-loopSkill
- pre-compactSkill
- pre-prSkill
- prove-control-bindsSkill
- prove-deploy-is-liveSkill
- reassert-guards-on-rewriteSkill
- reprobe-stale-premiseSkill
- scope-guardSkill
- shipSkill
- tdd-loopSkill
- tools-routerSkill
- triage-fanout-verdictsSkill
- xSkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 5 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
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
- Distinctive: line 85
Verify it on your own machine before trusting it:
- Distinctive: line 138
Read scope is unrestricted.
- Very specific: line 153
Add Bash to the matcher and deny it outright while the marker is active.
- Distinctive: line 167
Does not block reads.
- Distinctive: line 168
Does not distinguish subagent from main thread (the marker is session-global).
- Distinctive: line 169
Does not persist any allow/deny audit.
- Very specific: line 170
Does not survive a marker someone hand-edits into an unparseable state.
≈ “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
Read-Only Mode
Structural read-only rail for audit / research / census work. Activating it makes
the pretooluse-readonly PreToolUse hook DENY the file-editing tools
(Edit/Write/MultiEdit/NotebookEdit) for the rest of the session, until cleared.
Writes issued through the Bash tool are outside that matcher; see
What this does NOT do before relying on this for an
airtight audit.
It is the sister of scope-guard: scope-guard constrains where you may write;
read-only mode constrains whether you may write at all. The marker is opt-in,
so no marker means no enforcement, but once the marker exists the gate is
fail-CLOSED: anything it cannot evaluate is denied.
When to use
- An explicit audit / research / census where the file-editing tools should be structurally unavailable rather than merely discouraged.
- Backstopping read-only subagents with a hard structural rail, not just behavioral discipline (the PreToolUse payload carries no subagent identity, so the marker is session-global, enter read-only, do the read-only work, clear).
Optional plugin loading
Invoke /orion-skills:readonly for the plugin copy. Before running the examples,
choose the # Plugin: assignment below instead of the direct-install assignment.
Claude Code substitutes the plugin-root token in loaded Markdown; in a manual
terminal replace it with the actual absolute plugin path. It is not a guaranteed
shell environment variable. Loading the plugin registers no hooks: the marker
alone does not protect edits. The registration example below remains a separate
manual-install example; for a plugin, deliberately register the actual absolute,
shell-quoted hook path from the chosen plugin version and reverify after updates.
Activation: the marker lifecycle
The enforcement hook is INERT until the marker is set. Use a small helper to manage the marker; do not hand-write the marker JSON:
Paths below are written against the INSTALLED skill directory, because that is
where these run from in practice: the working directory is normally your project,
not this repo, so a bare skills/readonly/... path would not resolve. Set RO
once and the rest of this document works verbatim either way:
RO=~/.claude/skills/readonly # direct/manual install
# Plugin: RO="${CLAUDE_PLUGIN_ROOT}/skills/readonly"
# RO=./skills/readonly # or, working inside a clone of this repo
"$RO"/scripts/readonly-mode.sh on "audit: <what>" # enter read-only
"$RO"/scripts/readonly-mode.sh status # check
"$RO"/scripts/readonly-mode.sh off # leave (ALWAYS clear)on writes ~/.claude/state/readonly.json {"active":true,"reason":...}; the
hook then denies writes citing that reason. off removes it (writes allowed
again). No marker = no enforcement (safe default). Set READONLY_MARKER to
relocate the marker.
Mechanism (shipped, not left to the reader)
Both halves ship in this repo and are executable:
| File | Role |
skills/readonly/hooks/pretooluse-readonly.sh | PreToolUse hook that returns permissionDecision: deny |
skills/readonly/scripts/readonly-mode.sh | marker lifecycle (on / off / status) |
skills/readonly/selftest.py | fires the real hook and proves it denies |
Register the hook on the file-mutating tools:
{ "hooks": { "PreToolUse": [ {
"matcher": "Edit|Write|MultiEdit|NotebookEdit",
"hooks": [ { "type": "command",
"command": "$HOME/.claude/skills/readonly/hooks/pretooluse-readonly.sh" } ]
} ] } }Verify it on your own machine before trusting it:
python3 "$RO"/selftest.py # exit 0 = every case behavedFail-closed behavior
The hook is opt-in but not forgiving once opted in:
- Marker provably absent: inert, writes allowed. Read-only mode was never entered.
- Marker present and
{"active": true}: writes denied. - Marker present and
{"active": false}: writes allowed (explicit clear). - Marker present but unreadable, empty, malformed, a directory, or
activeholding any other value: writes denied. - Marker present but not a regular file (a named pipe, a device, a socket, or a
symlink to one) or larger than 64 KiB: writes denied, without waiting on or
reading the rest of it. Evaluation finishes promptly on local files: a hook that
hangs is killed by the harness, and a killed hook lets the write run. For the same
reason
readonly-mode.sh onrefuses a reason too long for its marker to fit, and the marker belongs on local disk (the default path): a regular file on a network or FUSE mount that stops responding can still stall the read, which no in-hook check can bound. - Every denial that is not an active marker explains itself: the hook exits 2
with a line on stderr naming the marker path and the
status/offrecovery commands, which the harness shows the agent. - The session directory cannot change the verdict: the hook's python runs
isolated (
python3 -I), so ajson.pyorjson/package in the repo being audited, or aPYTHONPATHentry, cannot replace the standard library inside the gate.statusruns the hook file directly, as the harness does, so a hook that lost its executable bit reports UNKNOWN instead of ON. - Marker absence unprovable (a directory on the marker path is not
searchable): writes denied, even if
onwas never run. The hook cannot tell "no marker" from "cannot look", and an armed marker may sit below the opaque directory. Recovery: restore search permission (chmod +x) on the marker path's ancestors, pointREADONLY_MARKERat a provable path, or unregister the hook;readonly-mode.sh statusreports this state asON (fail-closed).
That last row is the point. Under the Claude Code PreToolUse contract only
exit 0 with a deny decision, or exit 2, actually blocks; every other
non-zero exit lets the tool run. A gate that crashes on unexpected input
therefore fails open on exactly the input most likely to be hostile, so once the
marker exists every evaluation failure is routed to exit 2. "I could not tell"
is treated as "block", which is what fail-closed means operationally.
Discipline
- Set BEFORE the read-only work; clear AFTER; prefer clearing in the same turn
(or a trap) so a crash doesn't strand the session, and others, read-only. The
marker is a file on disk: a stranded
activemarker blocks writes in the NEXT session too until someone runsoff. - Read scope is unrestricted; only writes are blocked.
- No-hook environments: the hook is absent, apply read-only discipline manually (the marker is a no-op there).
What this does NOT do
It does not cover shell writes, and that is the boundary worth knowing. The
matcher registers the hook on Edit|Write|MultiEdit|NotebookEdit. Bash is a
separate tool, so rm, sed -i, cp, > file, or any script run through it
mutates files without this hook ever being consulted. Read the guarantee as
"the file-editing tools are blocked", not "nothing on disk can change".
Closing that gap fully is not a matter of adding Bash to the matcher and
moving on, because the honest options both cost something:
- Add
Bashto the matcher and deny it outright while the marker is active. Airtight, but it also blocksls,grep,git log, and the rest of the read-only work the mode exists to support. - Match on command shape (
rm,>,-i) and deny only those. This is a tripwire, not a wall: an alias,eval, a wrapper script, or the same binary reached by another path walks straight past a string match, and a matcher that finds nothing has established only that nothing matched.
The default here is the narrow, honest one. If your audit must be airtight
against shell writes, take the first option deliberately and accept that reads
through Bash go with it.
Also:
- Does not block reads.
- Does not distinguish subagent from main thread (the marker is session-global).
- Does not persist any allow/deny audit; it is a single on/off gate.
- Does not survive a marker someone hand-edits into an unparseable state; that
fails closed and blocks writes until fixed, which is deliberate but will look
like a bug if you are not expecting it (
readonly-mode.sh statussays so).
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.

