Skip to content

PrefixId double-wrap collapses full id to 8 chars in session resolvers #43

Description

@coryking

Problem

PrefixId(x) where x is already a PrefixId collapses its .full to the 8-char short form, because str.__new__(PrefixId, a_prefixid) routes through PrefixId.__str__ (which returns .short) rather than copying the full underlying buffer.

Repro:

from cc_explorer.utils import PrefixId
full = "f236adae-550d-47c0-8572-e7f8e88a4ee9"
a = PrefixId(full)
b = PrefixId(a)          # double-wrap
assert a.full == full     # ok (36 chars)
assert b.full == full     # FAILS — b.full == "f236adae" (8 chars)

Impact

SessionInfo.session_id (and the new _ArtifactSession.session_id) are already PrefixIds. Several resolvers re-wrap them before comparing:

  • mcp_server.py _resolve_unique_sessionPrefixId(s.session_id) == session
  • mcp_server.py (~line 891) — PrefixId(s.session_id) == sid
  • mcp_server.py _resolve_artifacts_corpusPrefixId(s.session_id) == raw_id

Because the wrapped side collapses to 8 chars, equality degrades to an 8-char prefix match. A caller that supplies a >8-char disambiguating prefix to pick one of two sessions sharing the same first 8 chars gets a spurious "ambiguous" error (or, in the unique path, a match that ignored the extra disambiguating chars). Full-id and ≤8-char-prefix lookups are unaffected, so it's latent.

The fix is to drop the redundant re-wrap (s.session_id == raw_id) since s.session_id is already a PrefixId whose __eq__ does prefix matching on the full value. Consider also guarding PrefixId.__new__ against re-wrapping a PrefixId so this can't recur.

Breadcrumbs

Found during the rewind full-corpus-parse fix (the new _ArtifactSession flows through _resolve_artifacts_corpus, which is where this surfaced). Behavior is identical for SessionInfo and _ArtifactSession, so it predates that change — kept out of that PR to stay focused.

Metadata

Metadata

Assignees

No one assigned

    Labels

    cc-explorerMCP server and typed JSONL toolkittech-debtCode quality, architecture smells, internal cleanup

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions