Skip to content

fix: read Claude Code tool_response in PostToolUse hook#578

Open
yixinforu wants to merge 2 commits into
rohitg00:mainfrom
yixinforu:fix/post-tool-use-tool-response
Open

fix: read Claude Code tool_response in PostToolUse hook#578
yixinforu wants to merge 2 commits into
rohitg00:mainfrom
yixinforu:fix/post-tool-use-tool-response

Conversation

@yixinforu
Copy link
Copy Markdown

@yixinforu yixinforu commented May 20, 2026

Summary

  • prefer tool_response for Claude Code PostToolUse payloads
  • keep the legacy tool_output fallback for existing hook payloads
  • add regression coverage for both payload shapes

Closes #539

Summary by CodeRabbit

  • Bug Fixes
    • Prioritizes the newer response field over the legacy field when extracting image data and constructing observation payloads, with a fallback to maintain compatibility.
  • Tests
    • Adds integration tests that validate payload mapping and successful execution for both the new response field and the legacy fallback.

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 20, 2026

@yixinforu is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c680606-7020-42b5-9609-0fabe19fc010

📥 Commits

Reviewing files that changed from the base of the PR and between 6d262b0 and ea7c7b4.

📒 Files selected for processing (1)
  • test/post-tool-use-hook.test.ts

📝 Walkthrough

Walkthrough

The post-tool-use hook now prefers Claude Code's tool_response (falling back to tool_output) when extracting image data and building the /agentmemory/observe payload. The change appears in both TypeScript and compiled JS and is covered by new integration tests for both paths.

Changes

Tool Response Fallback

Layer / File(s) Summary
Hook implementation: tool_response fallback
plugin/scripts/post-tool-use.mjs, src/hooks/post-tool-use.ts
The image-extraction step now uses data.tool_response when present, otherwise falls back to data.tool_output. Both the compiled JavaScript (lines 26–27) and TypeScript source (lines 34–36) are updated, with the source map directive refreshed.
Integration tests: tool_response and legacy paths
test/post-tool-use-hook.test.ts
New Vitest suite starts a local HTTP server, spawns the hook script with stdin payloads, and validates successful exit and correct webhook structure for both the new tool_response path and the legacy tool_output fallback.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • rohitg00/agentmemory#561: Parallel fix updating post-tool-use to prefer data.tool_response with data.tool_output fallback.

Poem

A rabbit hops through fields of code,
Sniffs out tool_response on the road,
Collects the image, builds the sight,
Sends the payload through the night,
Hooray — no more undefined! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: updating PostToolUse hook to read tool_response field instead of tool_output.
Linked Issues check ✅ Passed The PR successfully implements all coding objectives from issue #539: reads tool_response with fallback to tool_output, adds regression tests for both payload shapes, and fixes the cleanOutput undefined bug.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the tool_response/tool_output field mismatch in PostToolUse hook processing as specified in issue #539.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/post-tool-use-tool-response

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/post-tool-use-hook.test.ts (1)

26-77: ⚡ Quick win

Ensure test server cleanup runs on all failure paths.

server.close() should be in a finally block so rejected spawn/parse paths don’t leak the server and hang subsequent tests.

Suggested refactor
 async function runHook(payload: Record<string, unknown>) {
   let received: unknown;
   const server = createServer(async (req, res) => {
     const body = await readBody(req);
     received = JSON.parse(body);
     res.writeHead(200, { "Content-Type": "application/json" });
     res.end(JSON.stringify({ ok: true }));
   });

   await new Promise<void>((resolve) => {
     server.listen(0, "127.0.0.1", resolve);
   });

-  const address = server.address();
-  if (!address || typeof address === "string") {
-    throw new Error("test server did not bind to a TCP port");
-  }
-
-  const result = await new Promise<{
-    exitCode: number | null;
-    stderr: string;
-    stdout: string;
-  }>((resolve, reject) => {
-    const child = spawn(process.execPath, [HOOK], {
-      env: {
-        PATH: process.env["PATH"] ?? "",
-        AGENTMEMORY_URL: `http://127.0.0.1:${address.port}`,
-      },
-      stdio: ["pipe", "pipe", "pipe"],
-    });
-
-    let stdout = "";
-    let stderr = "";
-    child.stdout.on("data", (chunk) => {
-      stdout += chunk.toString();
-    });
-    child.stderr.on("data", (chunk) => {
-      stderr += chunk.toString();
-    });
-    child.on("error", reject);
-    child.on("close", (exitCode) => {
-      resolve({ exitCode, stderr, stdout });
-    });
-    child.stdin.end(JSON.stringify(payload));
-  });
-
-  await new Promise<void>((resolve, reject) => {
-    server.close((err) => (err ? reject(err) : resolve()));
-  });
-
-  return { ...result, received };
+  try {
+    const address = server.address();
+    if (!address || typeof address === "string") {
+      throw new Error("test server did not bind to a TCP port");
+    }
+
+    const result = await new Promise<{
+      exitCode: number | null;
+      stderr: string;
+      stdout: string;
+    }>((resolve, reject) => {
+      const child = spawn(process.execPath, [HOOK], {
+        env: {
+          PATH: process.env["PATH"] ?? "",
+          AGENTMEMORY_URL: `http://127.0.0.1:${address.port}`,
+        },
+        stdio: ["pipe", "pipe", "pipe"],
+      });
+
+      let stdout = "";
+      let stderr = "";
+      child.stdout.on("data", (chunk) => {
+        stdout += chunk.toString();
+      });
+      child.stderr.on("data", (chunk) => {
+        stderr += chunk.toString();
+      });
+      child.on("error", reject);
+      child.on("close", (exitCode) => {
+        resolve({ exitCode, stderr, stdout });
+      });
+      child.stdin.end(JSON.stringify(payload));
+    });
+
+    return { ...result, received };
+  } finally {
+    await new Promise<void>((resolve, reject) => {
+      server.close((err) => (err ? reject(err) : resolve()));
+    });
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/post-tool-use-hook.test.ts` around lines 26 - 77, The test can leak the
HTTP server when the spawn/parse Promise rejects; modify runHook so
server.close(...) is always called by moving the server.close call into a
finally block that runs after the spawn/parse Promise (the block that awaits the
child process result and assigns result), ensuring the server is closed whether
that Promise resolves or rejects; locate the runHook function and wrap the child
spawn/await logic in try { ... } finally { server.close((err) => err ?
reject(err) : null) } (or use await new Promise for close inside finally) so
server.close is invoked on all failure paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/post-tool-use-hook.test.ts`:
- Around line 26-77: The test can leak the HTTP server when the spawn/parse
Promise rejects; modify runHook so server.close(...) is always called by moving
the server.close call into a finally block that runs after the spawn/parse
Promise (the block that awaits the child process result and assigns result),
ensuring the server is closed whether that Promise resolves or rejects; locate
the runHook function and wrap the child spawn/await logic in try { ... } finally
{ server.close((err) => err ? reject(err) : null) } (or use await new Promise
for close inside finally) so server.close is invoked on all failure paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a28d08de-aab0-41d5-8a4e-ca302d1ef760

📥 Commits

Reviewing files that changed from the base of the PR and between edd1ceb and 6d262b0.

📒 Files selected for processing (3)
  • plugin/scripts/post-tool-use.mjs
  • src/hooks/post-tool-use.ts
  • test/post-tool-use-hook.test.ts

@yixinforu
Copy link
Copy Markdown
Author

Fixed. Moved the test server cleanup into a finally block so the server is closed even if the child process or request
handling path rejects.

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.

[BUG] PostToolUse hook reads data.tool_output but Claude Code sends tool_response — compresses fail XML schema (~47% loss)

1 participant