{
  "slug": "permission-hooks",
  "title": "Test the failure path of a permission hook",
  "dek": "A guard that handles “deny” correctly can still behave differently when its own code fails.",
  "category": "Controls",
  "minutes": 8,
  "decision": "Audit the exact pre-action guard and rehearse its failure behavior before relying on it.",
  "fit": "A Python hook in Claude Agent SDK is part of the path that controls a consequential action.",
  "skip": "You do not use the SDK, or the hook only adds optional telemetry. Preserve the ordinary authorization layer either way.",
  "steps": [
    [
      "Inspect the deployed pair",
      "Record the SDK, bundled CLI, hook event and actual configuration. The recovered source review identifies on_failure=\"block\" in Python SDK v0.2.177; its default remained continue. This is a dated feature reference, not a current-version recommendation."
    ],
    [
      "Check capability, not just configuration",
      "Confirm the runtime supports and applied the required behavior. The reviewed change describes refusing connection when blocking support is absent. Stop hooks have different behavior; do not generalize this setting to every hook."
    ],
    [
      "Use a harmless effect counter",
      "In an authorized isolated environment, use a local counter instead of a real message, payment or production change. Exercise allow, deny, exception, None, malformed response and timeout."
    ],
    [
      "Inspect effects and errors together",
      "Allow should increment the counter once. Deny and required failure cases should leave it unchanged and expose a detectable failure. Unsupported blocking capability should stop startup rather than continue silently."
    ]
  ],
  "acceptance": [
    "The valid path works; a system that denies everything does not pass.",
    "Each failure case preserves the required authorization boundary.",
    "Capability absence is visible and prevents use of the affected path."
  ],
  "stop": "Keep the action disabled or behind its existing human gate if a required case fails. Reverting to a fail-open configuration is not a safe rollback.",
  "limits": "The specific SDK change was rechecked against primary sources on 10 November 2026, but its behavior was not rerun for this reconstruction. Verify the current deployed SDK/CLI pair before use. Source review is not runtime validation.",
  "sources": [
    {
      "title": "Claude Agent SDK Python v0.2.177",
      "url": "https://github.com/anthropics/claude-agent-sdk-python/releases/tag/v0.2.177",
      "supports": "Primary release record for introduction of HookMatcher on_failure. Supports the dated feature claim, not a claim about your deployed configuration.",
      "review_date": "2026-11-10"
    },
    {
      "title": "HookMatcher on_failure change",
      "url": "https://github.com/anthropics/claude-agent-sdk-python/pull/1460",
      "supports": "Same-publisher implementation detail for blocking hook failure handling and capability checks. Not independent runtime validation.",
      "review_date": "2026-11-10"
    }
  ],
  "evidence": "Proposed worksheet",
  "updated": "2026-11-11",
  "scope_label": "Claude Agent SDK Python pre-action hooks",
  "search_terms": [
    "Claude",
    "SDK",
    "hooks",
    "timeout",
    "guard",
    "permissions",
    "fail closed",
    "exception"
  ],
  "evidence_plan_note": "Proposed evidence plan, not observed results or extra mandatory process. Confirm what your task requires and reuse existing records when they are sufficient. Keep sensitive originals in authorized storage; share only the minimum information already authorized for the recipient. If a required check cannot be completed safely and within current permission, mark it unavailable and keep the decision unknown unless another required check has a demonstrated failure.",
  "evidence_plan": [
    {
      "acceptance_check": 1,
      "collect": "Record the exact SDK/CLI configuration and a harmless allow-case trace with effect count before and after. The valid case should show one intended effect.",
      "not_enough": "A suite in which all actions are denied, or an allow response without inspecting the effect."
    },
    {
      "acceptance_check": 2,
      "collect": "Keep one result per deny, exception, None, malformed-response and timeout case: input, expected boundary, observed error and effect-counter delta. Use only the authorized isolated fake sink.",
      "not_enough": "A hook error or log message without checking whether the effect still happened."
    },
    {
      "acceptance_check": 3,
      "collect": "Record an unsupported-capability rehearsal and the resulting startup or connection refusal. Identify the SDK/runtime pair actually exercised.",
      "not_enough": "A configuration flag or release note by itself; neither proves the deployed pair enforces it."
    }
  ],
  "next_reads": [
    {
      "guide_id": "uncertain-effects",
      "when": "A failed or interrupted guard path leaves the underlying effect uncertain.",
      "purpose": "Separate the guard response from the authoritative operation outcome before retrying."
    },
    {
      "guide_id": "measure-agent-improvement",
      "when": "The required boundary works and you now need to compare its overhead or usability.",
      "purpose": "Measure the authorized change against the same quality and control requirements."
    }
  ],
  "id": "permission-hooks",
  "version": "release-2026-11-11-r12",
  "build_revision": "release-2026-11-11-r12",
  "source_sha256": "38252d3af9f8d111d69b1f9dfd0af85b3d9b26413c38ff1779c22c97ab3b8ad0",
  "url": "https://aomsites.com/guides/permission-hooks/",
  "content_type": "proposed-worksheet",
  "sources_rechecked": true,
  "source_review_scope": "Primary-source review of the dated SDK/runtime changes; no runtime tests.",
  "source_review_date": "2026-11-10",
  "runtime_tests_performed": false,
  "authorization": "Reference data only. Does not authorize any external action.",
  "formats": {
    "html": "https://aomsites.com/guides/permission-hooks/",
    "markdown": "https://aomsites.com/guides/permission-hooks/guide.md",
    "json": "https://aomsites.com/guides/permission-hooks/guide.json"
  },
  "local_paths": {
    "html": "/guides/permission-hooks/",
    "markdown": "/guides/permission-hooks/guide.md",
    "json": "/guides/permission-hooks/guide.json"
  },
  "offline_paths_from_pack_root": {
    "markdown": "guides/permission-hooks/guide.md",
    "json": "guides/permission-hooks/guide.json"
  },
  "decision_record": {
    "local_paths": {
      "html": "/decision-record/",
      "template": "/guides/permission-hooks/record-template.json",
      "markdown_template": "/guides/permission-hooks/record-template.md",
      "generic_template": "/decision-record/template.json",
      "example": "/decision-record/example.json",
      "schema": "/schemas/decision-record.schema.json",
      "checker": "/tools/check_record.py",
      "manifest": "/reading-pack-manifest.json"
    },
    "offline_paths_from_pack_root": {
      "template": "guides/permission-hooks/record-template.json",
      "markdown_template": "guides/permission-hooks/record-template.md",
      "generic_template": "decision-record/template.json",
      "example": "decision-record/example.json",
      "schema": "schemas/decision-record.schema.json",
      "checker": "tools/check_record.py",
      "manifest": "reading-pack-manifest.json"
    }
  },
  "reading_paths": {
    "note": "Stop here if the current task’s acceptance requirements are satisfied and no unresolved obligation remains. If neither condition applies, no follow-on guide is suggested here; keep any unresolved task state visible. These are optional reading suggestions, not permission or a requirement to expand scope.",
    "options": [
      {
        "guide_id": "uncertain-effects",
        "when": "A failed or interrupted guard path leaves the underlying effect uncertain.",
        "purpose": "Separate the guard response from the authoritative operation outcome before retrying.",
        "title": "Check the outcome before retrying",
        "local_paths": {
          "html": "/guides/uncertain-effects/",
          "markdown": "/guides/uncertain-effects/guide.md",
          "json": "/guides/uncertain-effects/guide.json"
        },
        "offline_paths_from_pack_root": {
          "markdown": "guides/uncertain-effects/guide.md",
          "json": "guides/uncertain-effects/guide.json"
        }
      },
      {
        "guide_id": "measure-agent-improvement",
        "when": "The required boundary works and you now need to compare its overhead or usability.",
        "purpose": "Measure the authorized change against the same quality and control requirements.",
        "title": "Measure improvement in accepted results",
        "local_paths": {
          "html": "/guides/measure-agent-improvement/",
          "markdown": "/guides/measure-agent-improvement/guide.md",
          "json": "/guides/measure-agent-improvement/guide.json"
        },
        "offline_paths_from_pack_root": {
          "markdown": "guides/measure-agent-improvement/guide.md",
          "json": "guides/measure-agent-improvement/guide.json"
        }
      }
    ]
  },
  "discovery": {
    "title": "Claude Agent SDK Python: test permission-hook failures",
    "description": "A proposed worksheet for Claude Agent SDK Python pre-action hooks: check deny, exception and timeout paths with a harmless counter. No runtime validation is claimed.",
    "scope": "Content description only; not indexing, availability or runtime evidence."
  },
  "section_map": [
    {
      "id": "decision",
      "label": "The decision",
      "json_pointer": "/decision",
      "markdown_heading": "The decision",
      "html_path": "/guides/permission-hooks/#decision"
    },
    {
      "id": "applies",
      "label": "Use this when",
      "json_pointer": "/fit",
      "markdown_heading": "Use this when",
      "html_path": "/guides/permission-hooks/#applies"
    },
    {
      "id": "skip",
      "label": "Skip this when",
      "json_pointer": "/skip",
      "markdown_heading": "Skip this when",
      "html_path": "/guides/permission-hooks/#skip"
    },
    {
      "id": "checks",
      "label": "Work through the checks",
      "json_pointer": "/steps",
      "markdown_heading": "Checks",
      "html_path": "/guides/permission-hooks/#checks"
    },
    {
      "id": "accept",
      "label": "What would count as success?",
      "json_pointer": "/acceptance",
      "markdown_heading": "Acceptance",
      "html_path": "/guides/permission-hooks/#accept"
    },
    {
      "id": "evidence",
      "label": "Evidence to bring back",
      "json_pointer": "/evidence_plan",
      "markdown_heading": "Evidence to bring back",
      "html_path": "/guides/permission-hooks/#evidence"
    },
    {
      "id": "stop",
      "label": "Stop or recover",
      "json_pointer": "/stop",
      "markdown_heading": "Stop or recover",
      "html_path": "/guides/permission-hooks/#stop"
    },
    {
      "id": "limits",
      "label": "What this does not establish",
      "json_pointer": "/limits",
      "markdown_heading": "Limits",
      "html_path": "/guides/permission-hooks/#limits"
    },
    {
      "id": "sources",
      "label": "Reference links",
      "json_pointer": "/sources",
      "markdown_heading": "Reference links",
      "html_path": "/guides/permission-hooks/#sources"
    },
    {
      "id": "record",
      "label": "Use this guide with your own evidence",
      "json_pointer": "/decision_record",
      "markdown_heading": "Start this guide’s record",
      "html_path": "/guides/permission-hooks/#record"
    },
    {
      "id": "next-reads",
      "label": "If another problem remains",
      "json_pointer": "/reading_paths",
      "markdown_heading": "If another problem remains",
      "html_path": "/guides/permission-hooks/#next-reads"
    }
  ],
  "acceptance_links": [
    {
      "acceptance_check": 1,
      "acceptance_html_path": "/guides/permission-hooks/#accept-1",
      "evidence_html_path": "/guides/permission-hooks/#evidence-1",
      "acceptance_json_pointer": "/acceptance/0",
      "evidence_json_pointer": "/evidence_plan/0"
    },
    {
      "acceptance_check": 2,
      "acceptance_html_path": "/guides/permission-hooks/#accept-2",
      "evidence_html_path": "/guides/permission-hooks/#evidence-2",
      "acceptance_json_pointer": "/acceptance/1",
      "evidence_json_pointer": "/evidence_plan/1"
    },
    {
      "acceptance_check": 3,
      "acceptance_html_path": "/guides/permission-hooks/#accept-3",
      "evidence_html_path": "/guides/permission-hooks/#evidence-3",
      "acceptance_json_pointer": "/acceptance/2",
      "evidence_json_pointer": "/evidence_plan/2"
    }
  ],
  "citation_note": "Section paths identify content on the canonical HTML page; they are not separate pages or evidence of live availability. Per-check numbers and JSON pointers apply to this build revision. Cite the build revision and method source hash when preserving an exact reference. Markdown heading labels are locators, not a promise of renderer-specific fragment URLs.",
  "source_fields": [
    "acceptance",
    "category",
    "decision",
    "dek",
    "evidence",
    "evidence_plan",
    "evidence_plan_note",
    "fit",
    "limits",
    "minutes",
    "next_reads",
    "scope_label",
    "search_terms",
    "skip",
    "slug",
    "sources",
    "steps",
    "stop",
    "title",
    "updated"
  ],
  "source_hash_scope": "SHA-256 of the build-input guide record serialized as JSON with sorted keys, compact separators and ASCII escaping. Not the downloadable guide.json file bytes."
}
