Skip to content

fix(adapter): don't emit permissionDecision for non-PreToolUse events#29

Open
srnnkls wants to merge 1 commit into
mainfrom
fix/cc-adapter-nonpermission-events
Open

fix(adapter): don't emit permissionDecision for non-PreToolUse events#29
srnnkls wants to merge 1 commit into
mainfrom
fix/cc-adapter-nonpermission-events

Conversation

@srnnkls

@srnnkls srnnkls commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Problem

fas eval --harness claude emits an invalid hook response for SessionEnd:

{"hookSpecificOutput":{"hookEventName":"SessionEnd","permissionDecision":"allow"}}

Claude Code rejects it with Hook JSON output validation failed — (root): Invalid input. hookSpecificOutput is a discriminated union keyed on hookEventName, and SessionEnd is not a valid variant — only PreToolUse carries a permissionDecision.

Root cause: ClaudeCode.RenderOutput treated permissionDecision as the default shape for every event outside a small contextOnlyEvents set. This also latently affected SessionStart, Notification, and PreCompact.

Fix

  • permissionEvents (PreToolUse) → permissionDecision shape.
  • contextEvents (UserPromptSubmit, PostToolUse, PostToolBatch, Stop, SubagentStart, SubagentStop) → additionalContext-only shape.
  • Everything else (session-lifecycle, notifications, unknown) → bare {}, which is valid against the CC schema.
  • Parse-error fallback: an unextractable hook_event_name now defaults to PreToolUse so the fail-open/fail-closed decision still renders under the one event that carries a permissionDecision.

Repro (before → after)

$ echo '{"hook_event_name":"SessionEnd",...}' | fas eval --harness claude
before: {"hookSpecificOutput":{"hookEventName":"SessionEnd","permissionDecision":"allow"}}   # rejected
after:  {}                                                                                    # valid

PreToolUse still decides: {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}.

Tests

  • New: bare events (SessionEnd/SessionStart/Notification/PreCompact/empty) omit hookSpecificOutput; context events omit permissionDecision.
  • Updated the HookEventName echo test (dropped SessionStart, now bare).
  • Full suite green (go test ./...); gofmt/vet/build pass.

Note: the pre-existing golangci-lint findings (gocritic/prealloc in unrelated files, also present on main) are out of scope and untouched here.

RenderOutput defaulted every event outside contextOnlyEvents to a
permissionDecision-carrying hookSpecificOutput. Session-lifecycle and
notification events (SessionEnd, SessionStart, Notification, PreCompact)
have no hookSpecificOutput variant in the Claude Code hook protocol, so
CC rejected the response ("(root): Invalid input") and the hook failed.

Make the permission shape opt-in for PreToolUse, keep the context shape
for context events, and render a bare {} for everything else. The
parse-error fallback defaults an unextractable event name to PreToolUse
so fail-open/fail-closed still renders a valid decision.
Copilot AI review requested due to automatic review settings July 2, 2026 09:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Claude Code adapter output so it conforms to the Claude Code hook response schema: only PreToolUse responses include permissionDecision, context-injection events emit a context-only variant, and all other events emit a bare {} (no hookSpecificOutput).

Changes:

  • Split hook events into permissionEvents (PreToolUse) vs contextEvents and render the correct JSON shape per category.
  • Render bare {} for session-lifecycle/notification/unknown events to avoid emitting invalid discriminated-union variants.
  • Default peekHookEventName to PreToolUse on parse-error fallback so fail-open/fail-closed still produces a decision response.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
internal/adapter/claudecode.go Adjusts Claude Code hook response rendering to emit schema-valid shapes per hook event kind.
internal/adapter/claudecode_test.go Updates/extends tests to cover bare events and context-only events, and adjusts hook-event echo expectations.
cmd/fas/main.go Changes parse-error fallback hook-event-name extraction to default to PreToolUse when unextractable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +351 to +355
var probe map[string]any
if err := json.Unmarshal(raw, &probe); err != nil {
t.Fatalf("emitted JSON not parseable: %v (raw=%s)", err, raw)
}
})
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.

2 participants