fix(memory-ingest): stop two silent transcript-ingest failures - #2699
Open
rayers wants to merge 1 commit into
Open
fix(memory-ingest): stop two silent transcript-ingest failures#2699rayers wants to merge 1 commit into
rayers wants to merge 1 commit into
Conversation
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
rayers
marked this pull request as ready for review
August 26, 2026 13:39
Two independent bugs made transcript pages silently fail to reach the brain.
1. Frontmatter fence gluing. buildTranscriptPage() built the closing "---"
with no trailing newline, and session bodies always start with "## ", so
the rendered page ended "...---## User". gbrain's frontmatter matcher
(/^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/ in src/core/markdown.ts) requires
the closing "---" to end its own line, so it skipped the glued fence,
latched onto the next standalone "---" in the transcript body, parsed the
prose between as YAML, and dropped the page with "Invalid YAML frontmatter".
Transcripts with no later "---" fell back to body-only, silently losing
their frontmatter. Fix: emit the fence on its own line with a blank
separator, matching renderPageBody()'s artifact branch.
2. Slug collisions. Two source files can map to one path-derived slug (a
session resumed under the same id on one day, or two ids sharing a 12-char
prefix). writeStaged() names each file "${slug}.md", so the second
overwrote the first; gbrain collected N-1 of N staged files and the
reconciliation guard failed the whole batch every run. Fix:
disambiguateSlugs() keeps the first occurrence and gives each later collider
a stable "-<sha8(source_path)>" suffix (deterministic, and slug + page_slug
move together so writeStaged, the failure mapping, and state recording agree).
Exports buildTranscriptPage, renderPageBody, and disambiguateSlugs for tests.
Adds regression tests for both failures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rayers
force-pushed
the
fix/transcript-ingest-silent-failures
branch
from
August 26, 2026 13:49
8957f30 to
9563469
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two independent bugs in
bin/gstack-memory-ingest.tsmake transcript pagessilently fail to reach the brain, so a user's Claude Code / Codex sessions never
become searchable and the transcripts source can get stuck failing every run.
1. Frontmatter fence gluing.
buildTranscriptPage()builds the closing---with no trailing newline, and a session body always starts with##(never a newline), so the rendered page ends
...---## User. gbrain'sfrontmatter matcher (
/^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/insrc/core/markdown.ts) requires the closing---to end its own line. It skipsthe glued fence, latches onto the next standalone
---in the transcript body(a markdown horizontal rule), parses the prose between as YAML, and drops the
whole page with
Invalid YAML frontmatter. Transcripts with no later---fallback to body-only, silently losing their frontmatter (title/type/tags/session
metadata). This has been present since the V1 transcript-ingest feature.
2. Slug collisions. Two source files can map to one path-derived slug
(
transcripts/<agent>/<repo>/<date>-<session_id[:12]>): a session resumed underthe same id on one day, or two session ids that share a 12-char prefix.
writeStaged()names each file${slug}.md, so the second overwrites the first.gbrain importthen collects N-1 of N staged files and the staged-vs-collectedreconciliation guard (correctly) fails the whole batch. It repeats every run
until the offending inputs age out ("accounted for N-1 of N staged ... Refusing
to advance state").
Live evidence
Before. In a real hourly auto-ingest, the transcripts source failed every
run with the reconciliation guard, and individual pages were dropped:
After. With both fixes, the same run reconciles fully and the previously
dropped pages import cleanly:
The staged page now closes its frontmatter on its own line:
Regression tests (added in this PR), run with
bun test:The fence test embeds gbrain's exact matcher and asserts the frontmatter closes
at the real fence (body prose stays out of YAML); it fails if the fix is
reverted. The slug tests assert deterministic disambiguation and that
non-colliding slugs are untouched. The existing
gstack-memory-ingest.test.tssuite still passes (42/42) with the three new exports.
Scope
bin/gstack-memory-ingest.ts(fence newline inbuildTranscriptPage; newdisambiguateSlugs()called inpreparePages;exportonbuildTranscriptPage,renderPageBody,disambiguateSlugs).unit tests and the existing memory-ingest suite.
staging path (unchanged by this diff).
ETHOS.md / voice / promotional changes.
eval/E2E jobs need base-repo secrets and will show empty-env auth failures
(expected for forks).
Liveness proof (required)
Checklist
GSTACK PRtyped live into a real surface (not edited onto the image)