agentleFS
OrionArchitektonOrionArchitekton/orion-skills/skills/readonly/SKILL.md

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

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
  1. Distinctive:

    Verify it on your own machine before trusting it:

    line 85
  2. Distinctive:

    Read scope is unrestricted.

    line 138
  3. Very specific:

    Add Bash to the matcher and deny it outright while the marker is active.

    line 153
  4. Distinctive:

    Does not block reads.

    line 167
  5. Distinctive:

    Does not distinguish subagent from main thread (the marker is session-global).

    line 168
  6. Distinctive:

    Does not persist any allow/deny audit.

    line 169
  7. Very specific:

    Does not survive a marker someone hand-edits into an unparseable state.

    line 170

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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:

FileRole
skills/readonly/hooks/pretooluse-readonly.shPreToolUse hook that returns permissionDecision: deny
skills/readonly/scripts/readonly-mode.shmarker lifecycle (on / off / status)
skills/readonly/selftest.pyfires 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 behaved

Fail-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 active holding 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 on refuses 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 / off recovery commands, which the harness shows the agent.
  • The session directory cannot change the verdict: the hook's python runs isolated (python3 -I), so a json.py or json/ package in the repo being audited, or a PYTHONPATH entry, cannot replace the standard library inside the gate. status runs 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 on was 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, point READONLY_MARKER at a provable path, or unregister the hook; readonly-mode.sh status reports this state as ON (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 active marker blocks writes in the NEXT session too until someone runs off.
  • 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 Bash to the matcher and deny it outright while the marker is active. Airtight, but it also blocks ls, 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 status says so).

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.