{"id":50559650,"url":"https://github.com/technicalpickles/claude-permissions-layer-receipts","last_synced_at":"2026-06-04T11:01:14.862Z","repository":{"id":353657549,"uuid":"1220123493","full_name":"technicalpickles/claude-permissions-layer-receipts","owner":"technicalpickles","description":"Test harness receipts backing the Permission Layers article on pickles.dev. Configs, prompts, and hook event logs from each cited run.","archived":false,"fork":false,"pushed_at":"2026-04-24T20:53:22.000Z","size":41,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-24T22:36:56.911Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/technicalpickles.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T15:12:00.000Z","updated_at":"2026-04-24T20:53:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/technicalpickles/claude-permissions-layer-receipts","commit_stats":null,"previous_names":["technicalpickles/claude-permissions-layer-receipts"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/technicalpickles/claude-permissions-layer-receipts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/technicalpickles%2Fclaude-permissions-layer-receipts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/technicalpickles%2Fclaude-permissions-layer-receipts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/technicalpickles%2Fclaude-permissions-layer-receipts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/technicalpickles%2Fclaude-permissions-layer-receipts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/technicalpickles","download_url":"https://codeload.github.com/technicalpickles/claude-permissions-layer-receipts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/technicalpickles%2Fclaude-permissions-layer-receipts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33901305,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-04T11:01:13.909Z","updated_at":"2026-06-04T11:01:14.843Z","avatar_url":"https://github.com/technicalpickles.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# claude-permissions-layer-receipts\n\nReceipts for a blog post. If [Claude Code's Permission Layers: A Map of the Territory](https://pickles.dev/claude-codes-permission-layers-a-map-of-the-territory/) says \"I ran X and got Y,\" the X and the Y are here.\n\nEach receipt is a small directory containing the Claude Code settings under test (`config.yml`), the prompt given to Claude (`prompt.md`), the outcome table where the harness produced one (`results.md`), and the raw `PreToolUse`/`PermissionRequest`/`PostToolUse` events logged by a hook (`raw/hook-events.jsonl`). A few probe-style receipts have `raw/prompt-events.log` instead; it's the same shape of data captured by a different runner.\n\nThe article cites ~10 receipts across six permission layers. Those receipts live here, one per article section, in a layer-oriented directory structure.\n\n## Versions\n\nTested against **Claude Code 2.1.116 and 2.1.117**, April 2026. Claude Code ships fast. If you're on a newer build and something here contradicts what you observe, trust the build. A PR adding a fresh run for a later version is welcome, see [Contributing](#contributing).\n\n## What's in each receipt\n\n```\n\u003clayer\u003e/\u003creceipt\u003e/\n├── README.md              # layer, article claim, finding, version\n├── config.yml             # sandbox + permissions + hooks config\n├── prompt.md              # prompt given to Claude\n├── results.md             # outcome table (most receipts)\n└── raw/\n    └── hook-events.jsonl  # PreToolUse/PermissionRequest/PostToolUse\n                           # (or prompt-events.log for probe-format receipts)\n```\n\nA few receipts don't have `results.md` because the test was short enough that the outcome is readable directly off the hook events. Those cases are noted in the receipt's own README.\n\n## Mapping: article section to receipt\n\n| Layer | Article claim | Receipt |\n|-------|---------------|---------|\n| 1, Rules | Edit prompts in `default` mode (not in allow list) | [`layer-1-rules/edit-default`](layer-1-rules/edit-default/) |\n| 1, Rules + mode | Same Edit runs silent in `acceptEdits` mode | [`layer-1-rules/edit-acceptedits`](layer-1-rules/edit-acceptedits/) |\n| 2, Sandbox | Happy path: `npx cowsay` runs silent with sandbox + `autoAllowBashIfSandboxed` on | [`layer-2-sandbox/happy-path-cowsay`](layer-2-sandbox/happy-path-cowsay/) |\n| 2, Sandbox | Write tool bypasses the sandbox; Bash doesn't | [`layer-2-sandbox/write-tool-bypass`](layer-2-sandbox/write-tool-bypass/) |\n| 3, Bash parser | Bail-list spot checks (simple_expansion, Unhandled string, braces, command substitution, compound, pipeline) | [`layer-3-parser/bail-list`](layer-3-parser/bail-list/) |\n| 3, Bash parser | `newline-hash` classification across Python and Node `-c`/`-e` probes | [`layer-3-parser/newline-hash`](layer-3-parser/newline-hash/) |\n| 4, Escape hatch | Natural retry: model sets `dangerouslyDisableSandbox` after a sandbox block | [`layer-4-escape-hatch/natural-retry`](layer-4-escape-hatch/natural-retry/) |\n| 4, Escape hatch | Bare `Bash` in `ask`: stays silent while sandboxed (workaround setup) | [`layer-4-escape-hatch/bare-bash-ask-sandboxed`](layer-4-escape-hatch/bare-bash-ask-sandboxed/) |\n| 4, Escape hatch | Same rule fires the moment Claude goes unsandboxed (workaround payoff) | [`layer-4-escape-hatch/bare-bash-ask-unsandboxed`](layer-4-escape-hatch/bare-bash-ask-unsandboxed/) |\n| 5, Hooks | `PreToolUse` hook with `^curl` blocks one command, silently allows `/usr/bin/curl` | [`layer-5-hooks/curl-block-anchor`](layer-5-hooks/curl-block-anchor/) |\n| 6, Auto mode | Classifier carries what the parser bails on (some of the time), punts on bare `$(...)` | [`layer-6-auto-mode/classifier-behavior`](layer-6-auto-mode/classifier-behavior/) |\n\n## About the test harness\n\nThe receipts came out of a local test harness that runs isolated Claude Code environments under different sandbox and permission configs and logs every tool call via `PreToolUse` hooks. The harness is built on `CLAUDE_CONFIG_DIR` (see [~/.claude/ Is Production](https://pickles.dev/dot-claude-is-production/) for how that works).\n\nThe harness itself isn't open-sourced yet. Publishing just the receipts first because they're what the article actually cites, and because they're reproducible on their own: each `config.yml` is a valid Claude Code settings file, each `prompt.md` is a real prompt you can paste into a fresh session, and the hook log gives you something concrete to compare against. The runner is a future announcement.\n\n## What got sanitized\n\nAbsolute paths, usernames, and session UUIDs were rewritten:\n\n- `/Users/josh.nichols/...` → `/Users/[you]/...`\n- `josh.nichols` → `[you]`\n- Session IDs (the UUIDs Claude Code generates per run) → `[session-id]`\n\nTiming data is preserved. Tool-use IDs (`toolu_...`) are preserved because they're noisy but not identifying. If you spot something that should have been scrubbed and wasn't, open an issue.\n\n## Contributing\n\nFresh runs on newer Claude Code versions are welcome. A useful PR:\n\n- Reruns one receipt under a specific Claude Code version, keeps the config and prompt the same\n- Adds the new `results.md` and `raw/hook-events.jsonl` alongside (or instead of) the existing ones\n- Notes in the receipt's README what the version was and whether the outcome changed\n\nIf Claude Code's behavior has drifted enough that the original finding no longer holds, that's the interesting case. Say so in the PR, and I'll pull it in.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechnicalpickles%2Fclaude-permissions-layer-receipts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftechnicalpickles%2Fclaude-permissions-layer-receipts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechnicalpickles%2Fclaude-permissions-layer-receipts/lists"}