Skip to content

fix: salvage summary data when Claude returns observation tags instead of summary tags#1604

Closed
octo-patch wants to merge 1 commit intothedotmack:mainfrom
octo-patch:fix/issue-1546-salvage-summary-from-observation-tags
Closed

fix: salvage summary data when Claude returns observation tags instead of summary tags#1604
octo-patch wants to merge 1 commit intothedotmack:mainfrom
octo-patch:fix/issue-1546-salvage-summary-from-observation-tags

Conversation

@octo-patch
Copy link
Copy Markdown
Contributor

Fixes #1546

Problem

When Claude responds to a summarize prompt with <observation> tags instead of <summary> tags (occurring ~72% of the time per the issue report), parseSummary logs a warning and returns null, discarding all the data Claude generated.

Solution

Instead of immediately returning null when <summary> is absent but <observation> is present, the parser now attempts to salvage useful data from the first observation block:

  • Maps <title>completed
  • Maps <narrative>learned (falls back to joining <fact> elements if narrative is absent)
  • Returns a minimal ParsedSummary with the salvaged fields, leaving unresolvable fields (request, investigated, next_steps) as null

If neither title nor learned data can be extracted, the function still returns null as before.

Testing

Added tests/parser.test.ts with 6 test cases covering:

  • Normal summary parsing
  • skip_summary tag handling
  • Salvage path: observation with title + narrative → minimal summary
  • Salvage path: observation with title + facts (no narrative) → joined facts as learned
  • No-op when observation tags contain no useful data
  • Returns null when neither summary nor observation tags present

All 6 tests pass. Pre-existing test suite unaffected.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 5, 2026

Summary by CodeRabbit

  • New Features

    • Parser now successfully reconstructs summary information from alternative data sources when standard parsing is unavailable, improving data recovery in edge cases.
  • Tests

    • Added comprehensive test suite validating parser behavior across multiple scenarios including successful parsing, fallback data handling, and edge cases where data sources are incomplete or unavailable.

Walkthrough

The parseSummary function now implements a fallback salvage mechanism: when <summary> tags are absent but <observation> tags exist, it reconstructs summary data by extracting title and deriving learned content from narrative or facts fields. Comprehensive test coverage validates both standard and fallback parsing paths.

Changes

Cohort / File(s) Summary
Parser fallback logic
src/sdk/parser.ts
Added observation tag detection and fallback reconstruction when summary parsing fails; extracts title/narrative/facts from observation blocks; updated logging to reflect salvage attempts and success.
Test coverage
tests/parser.test.ts
New test suite validating parseSummary behavior across multiple scenarios: null returns, successful summary parsing, and observation fallback reconstruction with various data combinations (narrative, facts, missing fields).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

Poem

🐰 A parser once blind to observations' grace,
Now salvages wisdom from every data space,
Where summaries hid in alternate forms,
Our fuzzy friend finds them—the new norm! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: salvaging summary data from observation tags when summary tags are absent.
Description check ✅ Passed The description clearly explains the problem, solution, and testing approach for the observation tag salvaging feature.
Linked Issues check ✅ Passed The PR successfully addresses the primary objective from issue #1546: extracting observation fields (title, narrative/facts) to construct minimal summaries instead of discarding data.
Out of Scope Changes check ✅ Passed All changes focus directly on the salvaging mechanism and its testing; the secondary objective (race condition handling during session completion) is not included, making the PR focused in scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/parser.test.ts (1)

57-93: Assert notes remains null in salvage-path tests.

At Line [70]-Line [76] and Line [90]-Line [93], add expect(result?.notes).toBeNull() to lock the minimal-salvage contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/parser.test.ts` around lines 57 - 93, In the two salvage-path tests
("salvages summary from observation tags when summary tag is absent (issue
`#1546`)" and "salvages summary using facts when observation has no narrative
(issue `#1546`)"), add an assertion to ensure the minimal-salvage contract by
appending expect(result?.notes).toBeNull() to each test after the existing
expectations; locate these cases around the parseSummary(...) calls and add the
assertion to verify parseSummary returns notes as null.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/parser.test.ts`:
- Around line 25-100: Add tests (in this suite or a companion test file) that
assert parseSummary handles three repository-level contracts: 1) uninitialized
memory session ID NULL — feed parseSummary input that simulates missing/empty
session_id and assert the returned object has session_id === null (reference
symbol: session_id, parseSummary); 2) upgrade version-tracking backward
compatibility — supply legacy upgrade/version markers and assert parseSummary
returns a normalized version field or upgrade_version consistent with current
format (reference symbols: version / upgrade_version, parseSummary); 3)
memory-exclusion prompt tag stripping — include privacy tags like <no_memory> or
<skip_memory> inside the input and assert those tags are removed from any
returned text fields and not leaked into the result (reference symbols:
no_memory, skip_memory, parseSummary); implement each as focused it(...) tests
mirroring the existing style and expectations in the parseSummary test suite.

---

Nitpick comments:
In `@tests/parser.test.ts`:
- Around line 57-93: In the two salvage-path tests ("salvages summary from
observation tags when summary tag is absent (issue `#1546`)" and "salvages summary
using facts when observation has no narrative (issue `#1546`)"), add an assertion
to ensure the minimal-salvage contract by appending
expect(result?.notes).toBeNull() to each test after the existing expectations;
locate these cases around the parseSummary(...) calls and add the assertion to
verify parseSummary returns notes as null.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 27e8dce9-8552-4d88-aad1-ce7efed67bc7

📥 Commits

Reviewing files that changed from the base of the PR and between 811c94d and b08794a.

📒 Files selected for processing (2)
  • src/sdk/parser.ts
  • tests/parser.test.ts

Comment thread tests/parser.test.ts
Comment on lines +25 to +100
describe('parseSummary', () => {
it('returns null when no summary or observation tags present', () => {
const result = parseSummary('some random text');
expect(result).toBeNull();
});

it('returns null when skip_summary tag is present', () => {
const result = parseSummary('<skip_summary reason="no work done" />');
expect(result).toBeNull();
});

it('parses a valid summary block', () => {
const text = `
<summary>
<request>Fix the bug</request>
<investigated>Found root cause in parser.ts</investigated>
<learned>The regex was greedy</learned>
<completed>Fixed the regex</completed>
<next_steps>Add tests</next_steps>
<notes>Low risk change</notes>
</summary>
`;
const result = parseSummary(text);
expect(result).not.toBeNull();
expect(result?.request).toBe('Fix the bug');
expect(result?.investigated).toBe('Found root cause in parser.ts');
expect(result?.learned).toBe('The regex was greedy');
expect(result?.completed).toBe('Fixed the regex');
expect(result?.next_steps).toBe('Add tests');
expect(result?.notes).toBe('Low risk change');
});

it('salvages summary from observation tags when summary tag is absent (issue #1546)', () => {
const text = `
<observation>
<type>observation</type>
<title>Fixed authentication bug in login handler</title>
<narrative>The root cause was a missing null check in the token validation logic. Added guard clause to prevent NPE.</narrative>
<facts>
<fact>Token validation was missing null check</fact>
<fact>Added guard clause in auth.ts line 42</fact>
</facts>
</observation>
`;
const result = parseSummary(text);
expect(result).not.toBeNull();
expect(result?.completed).toBe('Fixed authentication bug in login handler');
expect(result?.learned).toBe('The root cause was a missing null check in the token validation logic. Added guard clause to prevent NPE.');
expect(result?.request).toBeNull();
expect(result?.investigated).toBeNull();
expect(result?.next_steps).toBeNull();
});

it('salvages summary using facts when observation has no narrative (issue #1546)', () => {
const text = `
<observation>
<type>decision</type>
<title>Chose SQLite over PostgreSQL</title>
<facts>
<fact>SQLite has zero configuration</fact>
<fact>PostgreSQL requires a running server</fact>
</facts>
</observation>
`;
const result = parseSummary(text);
expect(result).not.toBeNull();
expect(result?.completed).toBe('Chose SQLite over PostgreSQL');
expect(result?.learned).toBe('SQLite has zero configuration; PostgreSQL requires a running server');
});

it('returns null when observation tags present but no useful data', () => {
const text = '<observation><type>observation</type></observation>';
const result = parseSummary(text);
expect(result).toBeNull();
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add the required repository-level test contracts in this suite (or companion suite).

This new test file does not currently validate uninitialized memory session ID NULL behavior, upgrade version-tracking backward compatibility, or memory-exclusion prompt tag stripping.

If you want, I can draft targeted test cases for those three contracts in a follow-up patch.
As per coding guidelines "All test files should validate against expected NULL values for uninitialized memory session IDs", "Test suites should validate backward compatibility for version tracking in upgrade detection", and "Privacy controls for memory exclusion should be validated through user prompt tag stripping tests".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/parser.test.ts` around lines 25 - 100, Add tests (in this suite or a
companion test file) that assert parseSummary handles three repository-level
contracts: 1) uninitialized memory session ID NULL — feed parseSummary input
that simulates missing/empty session_id and assert the returned object has
session_id === null (reference symbol: session_id, parseSummary); 2) upgrade
version-tracking backward compatibility — supply legacy upgrade/version markers
and assert parseSummary returns a normalized version field or upgrade_version
consistent with current format (reference symbols: version / upgrade_version,
parseSummary); 3) memory-exclusion prompt tag stripping — include privacy tags
like <no_memory> or <skip_memory> inside the input and assert those tags are
removed from any returned text fields and not leaked into the result (reference
symbols: no_memory, skip_memory, parseSummary); implement each as focused
it(...) tests mirroring the existing style and expectations in the parseSummary
test suite.

@anh-chu
Copy link
Copy Markdown

anh-chu commented Apr 6, 2026

this is essentially breaking claude-mem, rendering it useless!

alessandropcostabr pushed a commit to alessandropcostabr/claude-mem that referenced this pull request Apr 8, 2026
…tmack#1604)

Maps title→completed and narrative→learned when LLM responds with
<observation> instead of <summary> tags (~72% of cases).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
alessandropcostabr pushed a commit to alessandropcostabr/claude-mem that referenced this pull request Apr 8, 2026
…tmack#1604)

Maps title→completed and narrative→learned when LLM responds with
<observation> instead of <summary> tags (~72% of cases).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@thedotmack
Copy link
Copy Markdown
Owner

Closed during the April 2026 backlog cleanup. The underlying bug is now tracked in #1908, which is the single canonical issue being addressed by the maintainer. Thanks for taking the time to report — your symptoms and repro are captured in the consolidated ticket.

@thedotmack thedotmack closed this Apr 15, 2026
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.

Summary generation fails ~72% of the time: parser discards data + complete/summarize race

3 participants