Skip to content

fix(conversation-memory): correct PostToolUse hook input parsing - #487

Merged
baleen37 merged 2 commits into
mainfrom
fix/conversation-memory-hook-input
Feb 12, 2026
Merged

fix(conversation-memory): correct PostToolUse hook input parsing#487
baleen37 merged 2 commits into
mainfrom
fix/conversation-memory-hook-input

Conversation

@baleen37

@baleen37 baleen37 commented Feb 12, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes PostToolUse hook input parsing in conversation-memory plugin.

Root Cause: Claude Code hooks pass tool_input and tool_response fields, but the code was expecting a result field.

Changes

  • observe-cli.ts: Updated PostToolUseInput interface to use tool_input and tool_response instead of result. Merged both fields for compression to ensure all relevant data is captured.
  • inject-cli.ts: Added handling for empty stdin in SessionStart hook (Claude Code may not always send stdin data).
  • observe-cli.test.ts: Updated tests to match the new input format.

Test Plan

  • All existing tests pass (npm test in conversation-memory plugin)
  • BATS tests pass
  • Manual testing: node dist/cli.mjs inject now works without stdin
  • Manual testing: node dist/cli.mjs observe correctly parses hook input

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Global rate limiting for embedding and LLM requests to prevent spikes.
  • Configuration
    • New ratelimit settings added to configuration (per-service requests/sec and burst).
  • Bug Fixes
    • Improved session initialization when no input is provided—now creates a default session.
    • Observe CLI input now separates tool_input and tool_response for clearer tool logging.
  • Tests
    • Added comprehensive rate limiter tests and updated related unit tests to reflect input shape changes.

Claude Code hooks pass tool_input and tool_response fields, not result.
This fix updates observe-cli.ts to correctly parse the hook input format.

Changes:
- observe-cli.ts: Use tool_input + tool_response instead of result field
- inject-cli.ts: Handle empty stdin gracefully for SessionStart hook
- observe-cli.test.ts: Update tests to match new input format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Feb 12, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Handle empty stdin for SessionStart CLI; reshape PostToolUse input to separate tool_input and tool_response and merge them before storage; add a global token-bucket rate limiter and integrate it with embedding and LLM providers; tests updated/added to cover ratelimiter and embedding rate acquisitions.

Changes

Cohort / File(s) Summary
CLI — SessionStart
plugins/conversation-memory/src/cli/inject-cli.ts
Fallback for empty stdin: create default SessionStartInput using CLAUDE_SESSION_ID (or 'unknown') and empty transcript_path to avoid forcing JSON stdin parsing.
CLI — Observe / Tests
plugins/conversation-memory/src/cli/observe-cli.ts, plugins/conversation-memory/src/cli/observe-cli.test.ts
Replace result with tool_input and tool_response in PostToolUseInput; update handleObserve signature to accept both and merge inputs (primitive tool_response wrapped under result) before storage; tests updated accordingly.
Rate Limiter Module & Tests
plugins/conversation-memory/src/core/ratelimiter.ts, plugins/conversation-memory/src/core/ratelimiter.test.ts
Add token-bucket RateLimiter implementation, factory and singletons (getEmbeddingRateLimiter, getLLMRateLimiter), reset helper; comprehensive tests for token behavior, config integration, and concurrency.
Embedding integration & tests
plugins/conversation-memory/src/core/embeddings.ts, plugins/conversation-memory/src/core/embeddings.test.ts
Acquire embedding rate limiter before generating embeddings; tests add mock rate-limiter acquisition checks and reset between tests.
LLM config types
plugins/conversation-memory/src/core/llm/config.ts, plugins/conversation-memory/src/core/llm/config.test.ts
Add RateLimitConfig and RateLimitsConfig types; extend LLMConfig with optional ratelimit field; tests added to validate loading full/partial ratelimit config.
LLM providers integration
plugins/conversation-memory/src/core/llm/gemini-provider.ts, plugins/conversation-memory/src/core/llm/zai-provider.ts
Integrate LLM rate limiter acquisition at start of complete implementations for Gemini and Z.AI providers (await acquire() before making external calls).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibbled tokens, one by one, then paused,
Waiting gentle refill for work not lost,
I merged inputs and responses with a twitch,
Filled empty stdin with a name and a hop,
Memory stitched, rate-limited, and merry—pocketed like a carrot. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: correcting PostToolUse hook input parsing by replacing the result field with tool_input and tool_response fields in the conversation-memory plugin.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/conversation-memory-hook-input

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@plugins/conversation-memory/src/cli/observe-cli.ts`:
- Around line 64-69: The merge currently spreads toolResponse into mergedData
and can overwrite toolInput fields (see mergedData, toolInput, toolResponse), so
change the merge to preserve all input fields and namespace any object response
under a single result key (e.g., mergedData = { ...(toolInputObject), result:
toolResponseObject } and for primitives keep result: toolResponse); update the
logic used by compressToolData/compressBash to continue reading input fields
unchanged while the full response is available under result.
🧹 Nitpick comments (1)
plugins/conversation-memory/src/cli/observe-cli.test.ts (1)

396-401: Good edge-case coverage for missing tool_input and null tool_response.

Consider adding a test that exercises the actual merge logic in handleObserve — particularly the case where tool_input and tool_response share overlapping keys — to guard against silent data loss at the integration boundary.

Also applies to: 410-415

Comment on lines +64 to +69
const mergedData = {
...((toolInput && typeof toolInput === 'object') ? toolInput : {}),
...(typeof toolResponse === 'object' && toolResponse !== null ? toolResponse : {}),
// Include primitive responses as 'result' field
...(typeof toolResponse !== 'object' ? { result: toolResponse } : {}),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for compressToolData function definition
rg "function compressToolData|const compressToolData" -A 15 plugins/conversation-memory/src/

Repository: baleen37/claude-plugins

Length of output: 1320


🏁 Script executed:

#!/bin/bash
# Also search for where mergedData is used after being created
rg "mergedData" plugins/conversation-memory/src/cli/observe-cli.ts -B 5 -A 10

Repository: baleen37/claude-plugins

Length of output: 830


🏁 Script executed:

#!/bin/bash
# Check the full observe-cli.ts file around the lines in question
cat -n plugins/conversation-memory/src/cli/observe-cli.ts | sed -n '55,85p'

Repository: baleen37/claude-plugins

Length of output: 1310


🏁 Script executed:

#!/bin/bash
# Search for test files that show typical tool_input and tool_response structures
find plugins/conversation-memory -name "*.test.ts" -o -name "*.spec.ts" | head -5

Repository: baleen37/claude-plugins

Length of output: 332


🏁 Script executed:

#!/bin/bash
# Check the observe-cli test file to see test cases and typical tool structures
cat -n plugins/conversation-memory/src/cli/observe-cli.test.ts | head -150

Repository: baleen37/claude-plugins

Length of output: 6146


🏁 Script executed:

#!/bin/bash
# Look for specific compression functions to understand expected data shape
rg "function compressRead|function compressEdit|function compressWrite|function compressBash" -A 10 plugins/conversation-memory/src/core/compress.ts

Repository: baleen37/claude-plugins

Length of output: 1318


🏁 Script executed:

#!/bin/bash
# Check how handlePostToolUse uses mergedData
rg "handlePostToolUse" -A 15 plugins/conversation-memory/src/cli/observe-cli.ts

Repository: baleen37/claude-plugins

Length of output: 850


Merge logic silently overwrites tool_input fields when tool_response contains overlapping keys.

When tool_response (object) contains keys that also exist in tool_input (e.g., both have exitCode or file_path), the spread on line 66 overwrites the input values. This causes compressToolData to read corrupted or response data instead of input arguments that define what was executed.

Example: A Bash tool with input { command: "ls" } and response { exitCode: 0, stderr: "..." } would merge correctly. But if the response accidentally includes { exitCode: 1, ... }, the merge would lose the true input exitCode, and compressBash would compress misleading data.

Consider namespacing to preserve both:

Proposed fix
-    const mergedData = {
-      ...((toolInput && typeof toolInput === 'object') ? toolInput : {}),
-      ...(typeof toolResponse === 'object' && toolResponse !== null ? toolResponse : {}),
-      // Include primitive responses as 'result' field
-      ...(typeof toolResponse !== 'object' ? { result: toolResponse } : {}),
-    };
+    const mergedData = {
+      ...((toolInput && typeof toolInput === 'object') ? toolInput : {}),
+      result: toolResponse,
+    };

This preserves all input arguments (file_path, command, etc.) and places the complete response under a single result key. Compression functions already read only specific input fields and will work unchanged with the extra result key.

🤖 Prompt for AI Agents
In `@plugins/conversation-memory/src/cli/observe-cli.ts` around lines 64 - 69, The
merge currently spreads toolResponse into mergedData and can overwrite toolInput
fields (see mergedData, toolInput, toolResponse), so change the merge to
preserve all input fields and namespace any object response under a single
result key (e.g., mergedData = { ...(toolInputObject), result:
toolResponseObject } and for primitives keep result: toolResponse); update the
logic used by compressToolData/compressBash to continue reading input fields
unchanged while the full response is available under result.

…d LLM calls

Implements token bucket algorithm for rate limiting:
- Embedding generation: 5 req/sec, burst 10 (default)
- LLM API calls: 2 req/sec, burst 4 (default)

Features:
- Token bucket with configurable refill rate and burst capacity
- Non-blocking tryAcquire for immediate feedback
- Singleton instances for embedding and LLM rate limiting
- Configurable via ~/.config/conversation-memory/config.json

Configuration example:
{
  "ratelimit": {
    "embedding": { "requestsPerSecond": 5, "burstSize": 10 },
    "llm": { "requestsPerSecond": 2, "burstSize": 4 }
  }
}

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@baleen37
baleen37 enabled auto-merge (squash) February 12, 2026 07:40
@baleen37
baleen37 merged commit 32ca130 into main Feb 12, 2026
4 of 5 checks passed
@baleen37
baleen37 deleted the fix/conversation-memory-hook-input branch February 12, 2026 07:40
baleen-release-app Bot pushed a commit that referenced this pull request Feb 12, 2026
## [5.21.1](v5.21.0...v5.21.1) (2026-02-12)

### Bug Fixes

* **conversation-memory:** correct PostToolUse hook input parsing ([#487](#487)) ([32ca130](32ca130))
baleen37 pushed a commit to baleen37/memmem that referenced this pull request Feb 15, 2026
## [5.21.1](baleen37/bstack@v5.21.0...v5.21.1) (2026-02-12)

### Bug Fixes

* **conversation-memory:** correct PostToolUse hook input parsing ([#487](baleen37/bstack#487)) ([32ca130](baleen37/bstack@32ca130))
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.

1 participant