fix(claude-code): truncate memory prefix timestamp to second precision#45
fix(claude-code): truncate memory prefix timestamp to second precision#45ch-liuzhide wants to merge 1 commit into
Conversation
format_turn_memory() truncated the *default* timestamp to seconds but passed an explicit `timestamp` argument through verbatim. Replaying a historical turn with a transcript message field (millisecond precision + trailing `Z`, e.g. 2026-06-17T03:15:16.123Z) leaked sub-second digits into the stored memory prefix. Normalize the incoming timestamp via a new _second_precision() helper instead of trusting the caller. The helper rewrites a trailing `Z` to `+00:00` first — datetime.fromisoformat cannot parse a bare `Z` on Python 3.10 (our floor) — and falls back to a sub-second regex strip if parsing fails, so a malformed timestamp can never abort the Stop hook's memory write. Audited the sibling write paths: MCP write_memory passes content verbatim (no prefix), and the /ingest route stores the parsed timestamp in metadata.timestamp (consumed by temporal_boost), not in the content prefix — so format_turn_memory is the only content-prefix surface. Closes #38. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthrough
ChangesTimestamp second-precision normalization
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/hebb/integrations/claude_code/transcript.py (1)
166-188: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd a
Raisessection to this public API docstring.
format_turn_memoryis public and its docstring currently omits aRaisessection, which breaks the repo’s required docstring contract for public APIs.Suggested docstring patch
def format_turn_memory( @@ Returns: A human-readable string suitable for storing as a memory. + Raises: + None. """As per coding guidelines:
src/**/*.py: All public APIs in Python MUST have docstrings with Args, Returns, and Raises sections.🤖 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 `@src/hebb/integrations/claude_code/transcript.py` around lines 166 - 188, Add a Raises section to the public docstring for format_turn_memory in transcript.py so it satisfies the repo’s public API docstring contract. Update the existing docstring for this function to include a Raises block alongside Args and Returns, matching the documented behavior of format_turn_memory and any exceptions it may surface.Source: Coding guidelines
🤖 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.
Outside diff comments:
In `@src/hebb/integrations/claude_code/transcript.py`:
- Around line 166-188: Add a Raises section to the public docstring for
format_turn_memory in transcript.py so it satisfies the repo’s public API
docstring contract. Update the existing docstring for this function to include a
Raises block alongside Args and Returns, matching the documented behavior of
format_turn_memory and any exceptions it may surface.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3a461ea1-9196-461f-8fd4-48fe954289b5
📒 Files selected for processing (2)
src/hebb/integrations/claude_code/transcript.pytests/unit/integrations/test_claude_code_hooks.py
There was a problem hiding this comment.
Code Review
This pull request ensures that ISO-8601 timestamps used in memory prefixes are truncated to second-precision, removing sub-second noise. It introduces a helper function _second_precision to handle the truncation with a fallback regex mechanism for malformed timestamps, and adds corresponding unit tests. The review feedback recommends refining the fallback regular expression to use a lookbehind assertion to avoid accidentally stripping other dot-digit patterns (like version numbers or IP addresses) in malformed strings.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| # Matches the fractional-seconds group of an ISO-8601 timestamp (the only | ||
| # ``.<digits>`` run in such a string). Used to strip sub-second precision on | ||
| # the fallback path when full parsing fails. | ||
| _SUBSECOND_RE = re.compile(r"\.\d+") |
There was a problem hiding this comment.
The current regular expression \\.\\d+ is very broad and will strip any dot followed by digits in a malformed timestamp string (for example, version numbers like 1.2.3 or IP addresses would be corrupted to 1 or 12345678).\n\nTo make the fallback path more robust and ensure it only targets actual fractional seconds, we can use a lookbehind assertion to match only dots that follow the seconds field (i.e., preceded by a colon and two digits: (?<=:\\d{2})\\.\\d+).
| # Matches the fractional-seconds group of an ISO-8601 timestamp (the only | |
| # ``.<digits>`` run in such a string). Used to strip sub-second precision on | |
| # the fallback path when full parsing fails. | |
| _SUBSECOND_RE = re.compile(r"\.\d+") | |
| # Matches the fractional-seconds group of an ISO-8601 timestamp (the only\n# .digits run in such a string). Used to strip sub-second precision on\n# the fallback path when full parsing fails.\n_SUBSECOND_RE = re.compile(r"(?<=:\\d{2})\\.\\d+") |
Summary
Closes #38. Memory-content timestamp prefixes are now always second-precision, regardless of whether the timestamp is generated internally or passed in.
format_turn_memory()truncated only the default (datetime.now(...)) timestamp; an explicittimestampargument was passed through verbatim. Replaying a historical turn with a transcript message field (millisecond precision + trailingZ, e.g.2026-06-17T03:15:16.123Z) leaked sub-second digits into the stored prefix.Change
A new
_second_precision()helper normalizes any incoming timestamp before it reaches the[…]prefix:Zto+00:00before parsing —datetime.fromisoformat()cannot parse a bareZon Python 3.10 (ourrequires-pythonfloor), and the documented leak input is exactly…123Z. The naivefromisoformat(timestamp)from the issue's suggested fix would raiseValueErrorinside the Stop hook for that input, aborting the memory write.2026-06-17T03:15:16.123Z→2026-06-17T03:15:16+00:00; non-UTC offsets preserved (…16.500+05:30→…16+05:30).Audit of sibling write paths (issue point 2)
write_memory— passescontentverbatim; no timestamp prefix./ingestroute (server/routers/memories.py) — stores the parsed turn timestamp inmetadata.timestamp(consumed bytemporal_boost), not in the content prefix; out of scope, left untouched to avoid changing temporal-boost behavior.write_memory.format_turn_memory()is the only content-prefix surface.Tests
Added 6 cases in
TestFormatTurnMemory: default prefix has no sub-seconds, millisecond/microsecondZtruncation, already-clean passthrough, offset preservation, and the non-raising malformed-timestamp fallback. Full unit suite green (583 passed, 1 skipped); mypy + ruff clean on the changed files.🤖 Generated with Claude Code
Summary by CodeRabbit