Skip to content

search: the miner stores owner/repo#N with a junk owner when a tool-input newline precedes it #155

Description

@grimmerk

The transcript miner stores some references under a wrong owner: nacme/service#4242 instead of acme/service#4242. Found 2026-09-15 while checking whether issue URLs are indexed (they are, see #157); the mangled entries turned up in the cache itself.

Owner, repo and issue numbers in this issue are placeholders. The real ones come from a private work repository and are deliberately not reproduced here; the counts below are real.

Cause

assistantTextOfLine takes a tool call's input with JSON.stringify(b.input) (src/enrichment-cache.ts), and JSON.stringify re-encodes a newline inside a string value as the two characters \ and n. So a tool input that reads

…some text
acme/service#4242…

reaches the scanner as …some text\nacme/service#4242… with a literal n sitting immediately before the owner.

PR_REF_RE has three branches, and only the second one has no left boundary:

branch form left boundary
1 github.com/o/r/pull|issues/N (?<=^|:\/\/|[^a-z0-9_./-])
2 owner/repo#N none
3 bare #N (?:^|[^A-Za-z0-9&#])

[A-Za-z0-9-]+ is greedy and starts one character early, so the n becomes part of the owner.

This is a two-implementations-of-one-rule drift: the query side already gets it right. prRefPatterns' hash pattern in src/session-search.ts is (?:^|[^0-9a-z_./-])((?:${REPO})?)#${n}(?![0-9a-z_]), and its comment even spells out the case ("so foo/bar#12 captures foo/bar, not o/bar"). The miner's own comment block claims both sides apply the same boundaries, which is currently false.

What it costs, measured on the live cache (2026-09-15)

~/.config/codev/enrichment-cache.json, version 2, 134 sessions:

references stored 6378
stored with a junk owner 139 (2.2%), all of them an n glued on the front
sessions affected 32 of 134
references whose clean form is absent from the same session 5

The last row is the real damage. A repo-qualified query builds a pattern with a left boundary, so it cannot match a stored nacme/service#4242, and for those five the mangled string is the only form that session carries, so the session is unfindable by its correct qualified form. Bare #N and pr:N are unaffected: #N is repo-agnostic, and pr:N only needs some qualified mention, which a mangled one still is.

The junk prefix was n in 139 of 140 cases, across two unrelated owners, so the class is general rather than tied to any one repository.

Fix

Add the same left boundary to branch 2, mirroring the matcher:

(?<![A-Za-z0-9_./-])([A-Za-z0-9-]+\/[A-Za-z0-9_.-]+)#([1-9][0-9]*)(?![A-Za-z0-9_])

Then bump ENRICHMENT_CACHE_VERSION from 2 to 3 so the already-stored mangled entries are dropped and re-mined (readEnrichmentCacheFile discards the file on a version mismatch). A full re-mine of the corpus was measured at ~2.0s once, in the background pass, so the one-off cost is not user-visible.

Test to add: a fixture whose assistant record carries a tool input with a newline before an owner/repo#N mention, i.e. the real JSON.stringify shape rather than a hand-written string, asserting the owner comes out clean. The same fixture guards the general lesson: anything scanned after JSON.stringify sees escape sequences as literal letters, not whitespace.

Severity: Minor. It silently narrows the index rather than breaking anything visible, which is why it went unnoticed since #151.

🤖 On behalf of @grimmerk — generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions