Skip to content

fix(auto-recall): thread parsed STDIN through the hook-dispatch path#61

Open
hobostay wants to merge 1 commit into
Tencent:mainfrom
hobostay:fix/auto-recall-dispatcher-stdin
Open

fix(auto-recall): thread parsed STDIN through the hook-dispatch path#61
hobostay wants to merge 1 commit into
Tencent:mainfrom
hobostay:fix/auto-recall-dispatcher-stdin

Conversation

@hobostay

Copy link
Copy Markdown
Contributor

Summary

Auto-recall never fires when invoked through the hook dispatcher — i.e. in every real installation. autoRecall() reads process.stdin itself, but teamai hook-dispatch (the only hook command injected today) reads STDIN once, parses it, and passes the parsed object to each handler. By the time the auto-recall handler runs, process.stdin is drained, so autoRecall() sees "no STDIN data" and returns immediately.

The handler's own code documented this as a known limitation:

// We can't easily pipe stdin to the function, so for this handler
// we'll rely on the environment (process.stdin being piped from Claude Code).
// TODO: Refactor autoRecall to accept parsed data directly.
...
await autoRecall();   // ← called with no args; ignores the `stdin` param

The teamai auto-recall --stdin direct command still works (it owns the stream), but cleanupLegacyHooks removes those legacy entries, so the live PostToolUse path goes through hook-dispatch → broken.

Fix (resolves the TODO)

  1. Extract the HookInput construction out of readStdin into a pure parseHookInput(data), so a caller that already holds the parsed JSON can build the input without touching the stream.
  2. autoRecall(input?) accepts an optional pre-parsed input; the legacy direct command still falls back to readStdin() — unchanged.
  3. The handler now parseHookInput(stdin)s the dispatcher's object and passes it to autoRecall(input).

The existing stdout-intercept capture is retained (and is sound: log.debug is off by default and autoUpvote only uses log.debug/log.error-to-stderr, so the additionalContext JSON is the last stdout write).

Tests

  • Added a regression test in hook-handlers.test.ts that executes the auto-recall handler with a realistic PostToolUse STDIN object and asserts autoRecall is called with a correctly-parsed input (toolName/toolInput/toolOutput/sessionId). It fails on the old handler (expected undefined to be definedautoRecall was invoked with no arguments).
  • Used importOriginal in the module mock so the real parseHookInput runs (only autoRecall is stubbed).
  • The legacy direct command and all existing auto-recall/hook-dispatch tests are unchanged. npm test → 1441 passed; tsc --noEmit → clean.

Co-Authored-By: Claude noreply@anthropic.com

autoRecall() read process.stdin itself (via readStdin). The hook dispatcher
(hook-dispatch-cli.ts) already reads STDIN once, parses it, and fans the
parsed object out to every handler — so by the time the auto-recall handler
ran, process.stdin was drained and autoRecall() saw "no STDIN data" and
returned immediately. The PostToolUse hooks that ship today all go through
`teamai hook-dispatch` (legacy `teamai auto-recall --stdin` entries are
removed by cleanupLegacyHooks), so auto-recall never fired in production.

The handler even carried a TODO noting it "can't easily pipe stdin to the
function" and worked around it by capturing stdout — but never fed it data.

Fix (resolves that TODO):
- Extract the HookInput construction from readStdin into a pure
  parseHookInput(data) so callers that already hold the parsed JSON can
  build the input without touching the stream.
- autoRecall(input?) accepts an optional pre-parsed input; the legacy
  `teamai auto-recall --stdin` command path still falls back to readStdin.
- The handler now parseHookInput(stdin)s the dispatcher's object and passes
  it to autoRecall(input) instead of calling it with no args.

Added a regression test that executes the auto-recall handler and asserts
autoRecall receives a correctly-parsed input (toolName/toolInput/toolOutput/
sessionId). It fails on the old handler (`expected undefined to be defined`
— autoRecall was called with no args).

The legacy direct command and all existing auto-recall tests are unchanged.
`npm test` 1441 passed; `tsc --noEmit` clean.

Co-Authored-By: Claude <noreply@anthropic.com>
@Tencent Tencent deleted a comment from hsuchifeng Jun 28, 2026
@hsuchifeng

Copy link
Copy Markdown

I noticed this PR currently has conflicts with the base branch, and GitHub marks it as not mergeable. Could you please sync with the latest main and resolve the conflicts first? I can continue the review/merge after that.

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