Skip to content

fix(hooks): dispatch wrapped hook payloads without rendering - #641

Open
vishnujayvel wants to merge 1 commit into
sirmalloc:mainfrom
vishnujayvel:oss-review/occ-5u5
Open

vishnujayvel wants to merge 1 commit into
sirmalloc:mainfrom
vishnujayvel:oss-review/occ-5u5

Conversation

@vishnujayvel

Copy link
Copy Markdown
Contributor

fix(hooks): dispatch wrapped hook payloads without rendering

Closes #623. Thanks to the reporter of #623 for the reproduction.

When a configured statusLine command is wrapped (e.g. bash -c '...'), syncWidgetHooks() appends --hook outside the wrapper's quotes, so the inner ccstatusline never sees the flag. Its hook payload then arrives on the piped-stdin path and renders as status output instead of being recorded as a skill-usage event. This change detects supported hook envelopes on that path and dispatches them through the existing hook handler without rendering. Command construction is unchanged.

Type: bug fix · Risk: low — only payloads with hook_event_name PreToolUse or UserPromptSubmit and a non-empty session_id are diverted; everything else takes the existing path.

File What it does now
src/ccstatusline.ts Adds isDispatchableHookPayload(); in piped-input handling, dispatches matching payloads via handleHookInput and returns before StatusJSONSchema validation
src/__tests__/ccstatusline-hook-dispatch.test.ts New: spawns the CLI as a subprocess and covers dispatch, explicit --hook regression, status rendering, non-dispatchable Stop, and malformed JSON

How verified:

  • bun install --frozen-lockfile → exit 0 (lockfile unchanged), darwin.
  • bun test src/__tests__/ccstatusline-hook-dispatch.test.ts → 6 pass, 0 fail, 17 expect() calls, 0 skipped on darwin.
  • Not run: full test suite, bun run lint, typecheck, build, and Windows (the file is skipped on win32, so Windows is untested).
Evidence & notes for reviewers

Root cause. syncWidgetHooks() in src/utils/hooks.ts builds the hook command as `${statusCommand} --hook`. For a wrapped command such as bash -c '<cmd> ccstatusline', the appended --hook becomes an argument to bash, not to the wrapped ccstatusline. StatusJSONSchema is a loose object with all fields optional, so the hook payload validates as status JSON and renders.

Approach. Route the payload on its content, not on argv or the environment: hook_event_name must be PreToolUse or UserPromptSubmit and session_id a non-empty string. The check runs before StatusJSONSchema.safeParse. The explicit --hook branch is still checked first and is unchanged. No shell parsing, no new environment contract, and no change to src/utils/hooks.ts, src/utils/hook-handler.ts, or src/types/StatusJSON.ts.

Alternatives not taken. Setting an env var such as CCSTATUSLINE_HOOK=1 in the hook command was rejected: it breaks cmd.exe/PowerShell quoting and fails wrappers that clear the environment. Fixing command construction is a separate problem; open PR #502 touches construction but still appends --hook outside the wrapper quotes, so it does not cover this payload path.

Test design. ccstatusline.ts calls void main() at module load (reads stdin, may launch the TUI), so the test spawns the real entry via bun and redirects HOME/USERPROFILE to a temp dir per test. Assertions for dispatch check that the skill event is written to ~/.cache/ccstatusline/skills/, not only that stdout is empty. The suite is skipped on win32 as a precaution: it runs bun via execFileSync without shell: true, and no Windows runtime was available to confirm that resolves the same way. This is not a claim that the fix is POSIX-only.

Verification identity. Committed blobs: src/ccstatusline.ts 3dfe9c56, test file 3eebecfd. The focused verification ran on the same staged content; the verification logs do not record file hashes, so identity rests on the files being unchanged since that run, which the packaging step did not modify.

Logs. Verification logs and before/after resource records are in the local artifact directory for this candidate (occ-5u5-verify-*), not attached to the PR.

Out of scope. No CURRENT_VERSION or settings-migration bump, since the Settings schema is untouched.


🤖 Drafted with Claude Code (Claude Sonnet 5). The focused test run above was executed locally by the agent; the draft has not been reviewed by a human maintainer and has not been submitted.

When a configured statusLine command is wrapped (e.g. bash -c '...'),
syncWidgetHooks() appends --hook outside the wrapper's quotes, so the
inner ccstatusline never sees the flag. The hook JSON then arrives on
the piped-stdin path and renders as status output.

Detect supported hook envelopes (PreToolUse / UserPromptSubmit with a
non-empty session_id) before StatusJSONSchema validation and route them
through the existing handleHookInput, without rendering. Explicit --hook
mode is unchanged.

Refs sirmalloc#623

Co-Authored-By: Claude Sonnet 5 <[email protected]>
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.

Hook command breaks when the statusLine command is wrapped (--hook appended outside bash -c quotes)

1 participant