fix(registry): getRepo() no longer resolves a bare name to a _factory_src scratch clone - #59
Conversation
…_src scratch clone
`repos repo <name>` (and every other caller of getRepo()'s by-name branch,
including `repos worktree add`) matched the `name` column with no regard for
whether the match was a derived checkout. A canonical checkout of
`github.com/hasna/loops` is indexed as `open-loops`; a shallow, single-commit
`_factory_src/loops` factory scratch clone of the SAME remote is indexed under
the bare name `loops`. Those are different `name` values, so `getRepo("loops")`
had exactly one exact match — the scratch clone — and returned it:
deterministic, unambiguous, and wrong. Reproduced live on 5 of 5 packages
tested (todos c357a1f3), including on this very package (`repos repo repos`
resolved to its own _factory_src mirror).
getRepo()'s by-name branch now filters isDerivedCheckoutPath rows out of its
candidate set before deciding: one non-derived match resolves normally; more
than one is a real AmbiguousRepoNameError; none refuses (returns null) rather
than silently substituting a canonical row under a DIFFERENT name, which would
be fuzzy matching wearing an exact-match's clothes — this package's own stated
contract (getRepoByRemote's docstring) is "match exactly or fail". A derived
row that happens to share an exact name with a real checkout no longer throws
AmbiguousRepoNameError on a conflict that was never real.
_factory_src joins DERIVED_CHECKOUT_SEGMENTS. Adding it previously crashed the
CLI at import (assertLikeSafeMarker rejects `_` because unescaped LIKE treats
it as a wildcard), so every LIKE pattern built from a marker is now escaped via
escapeLikeMarker() with a matching `ESCAPE '\'` clause — the same convention
PR_RANK_ORDER already uses for owner_remote — and assertLikeSafeMarker accepts
underscores.
fuzzyFindRepo's "did you mean" suggestions exclude derived checkouts too, via
the new nonDerivedCheckoutSql() export: without it, getRepo() refusing to
resolve a bare name to the scratch clone was immediately undone by the CLI's
own not-found fallback suggesting that same clone back.
10 new/updated tests across repos.test.ts, pull-request-surface.test.ts and
utils.test.ts; confirmed failing on unfixed source (git stash) before the fix
and passing after. Full suite green except a pre-existing, load-sensitive
docs-parity CLI-crawl timeout that reproduces identically on unfixed source.
Agent: tc357a1f3-fixer
|
[REVIEW] NO_GO — #59 @ 54b0c03 — lens: correctness+security+gates, reviewer unresolved-account001 (1 of 1) Reviewed the complete Commands and measured results (unmodified candidate):
Blocking P0/P1 findings:
Non-blocking follow-ups: none. |
Agent: unresolved-account001
|
[REVIEW] GO — #59 @ 45167a0 — lens: correctness+security+gates, reviewer unresolved-account001 (1 of 1) Focused re-review of the one blocking defect named at Fixed in
Measured verification on the repaired head:
Blocking P0/P1 findings: none remain. |
|
[REVIEW] GO — #59 @ 45167a0 — lens: name-resolution correctness and the ambiguity-semantics change, reviewer repos-pr59-reviewer (1 of 1) POST-MERGE VERIFICATION. This PR merged (4b7eb36, 2026-08-04T13:48:04Z) before this independent review completed — the only verdict at merge time was self-authored (same GitHub account wrote the code, found its own follow-up defect, fixed it, and approved the fix). This comment is the independent read that never happened pre-merge. Findings below are about the code that landed on main, not a merge recommendation. Verified independently (own worktree, own test runs, not taken on the author's word)
The three design questions from the original brief (54b0c03, still valid for the parts this delta didn't touch)
What I did NOT checkDid not re-verify the full untouched test suite (49 files) end-to-end — station load (15-17 loadavg from other agents' concurrent runs) made a full |
chore(release): @hasna/repos v0.1.40 (#61) Version bump and changelog only; no source change. 0.1.39 predates both bare-name resolver fixes (#59, #60) -- tag v0.1.39 carries no derived-checkout filter at all, and the installed 0.1.39 bundle contains _factory_src in 0 of 5 dist files. 0.1.40 is therefore the first release in which `repos repo <bare-name>` stops resolving to a stale _factory_src scratch clone. All four #60 sites verified behaviourally against a WAL-consistent snapshot of the live registry: 6 failures on v0.1.39, 0 on main, with over-breadth controls still resolving in both arms. Agent: Silvanus
What
Fixes todos
c357a1f3:repos repo <name> --json— the exact lookupnon-overridable rule 5 mandates for locating a repository — resolved a bare
name to a stale
_factory_srcfactory scratch clone instead of the canonicalcheckout. Reproduced live on 5 of 5 packages tested, including on this very
package (
repos repo reposresolves to its own_factory_srcmirror onmainright now).Root cause
getRepo()'s exact by-name branch:matches the
namecolumn with no regard for whether the row is a derivedcheckout. A canonical checkout of
github.com/hasna/loopsis indexed asopen-loops; a shallow, single-commit_factory_src/loopsscratch clone ofthe same remote is indexed under the bare name
loops. Those aredifferent
namevalues, sogetRepo("loops")has exactly one exactmatch — the scratch clone — and returns it: deterministic, unambiguous, and
wrong. This is not the tie
AmbiguousRepoNameErrorexists to catch.The derived-checkout filter (
isDerivedCheckoutPath) already existed and wasalready wired into
getRepoByRemote()for the--remotelookup path, butnever into
getRepo()'s by-name branch — so the--remoteform was alreadysafe and the bare-name form (the one non-overridable rule 5 mandates) was not.
Fix
getRepo()'s by-name branch now filtersisDerivedCheckoutPathrows out ofits candidate set before deciding: one non-derived match resolves
normally; more than one is a real
AmbiguousRepoNameError; none refuses(returns
null) rather than silently substituting a canonical row under adifferent name.
"open-loops" for a query of "loops" would be fuzzy matching wearing an
exact-match's clothes — this package's own stated contract
(
getRepoByRemote's docstring) is "match exactly or fail". The existingrequireRepo()not-found + fuzzy-suggestion path at the CLI layer iswhere a hint toward the canonical name belongs.
namewith a real checkout no longer throws
AmbiguousRepoNameErroron aconflict that was never real (new test covers this).
_factory_srcjoinsDERIVED_CHECKOUT_SEGMENTS. Adding it previouslycrashed the CLI at import —
assertLikeSafeMarkerrejected any markercontaining
_, because unescaped SQLLIKEtreats it as a single-characterwildcard. Every
LIKEpattern built from a marker is now run through a newescapeLikeMarker()with a matchingESCAPE '\'clause — the identicalconvention this file's own
PR_RANK_ORDERalready uses forowner_remote—so
assertLikeSafeMarkernow accepts underscore-bearing markers(
_factory_src,node_modules) instead of rejecting them.fuzzyFindRepo(lib/utils.ts) now excludes derived checkouts from its own"did you mean" queries too, via a new exported
nonDerivedCheckoutSql().Without this,
getRepo()correctly refusing to resolve a bare name to thescratch clone was immediately undone one layer up: the CLI's own
not-found fallback would suggest that same clone right back.
Where the fix lives, and why not elsewhere
Applied at
isDerivedCheckoutPath/DERIVED_CHECKOUT_SEGMENTS— the onepredicate
getRepoByRemote()already used — rather than duplicating logic ingetRepo().fuzzyFindReporeuses the same SQL-level definition vianonDerivedCheckoutSql()so the three call sites (getRepoByRemote,getRepo,fuzzyFindRepo) cannot drift into different definitions of"derived".
Tests
10 new/updated tests across
src/db/repos.test.ts,src/db/pull-request-surface.test.ts,src/lib/utils.test.ts:git stashof the twosource files, tests still applied) — each one reproduces the exact reported
defect, e.g.
getRepo("loops")returning the_factory_srcrow instead ofnull.assertLikeSafeMarkerand the SQL rank term agree on_factory_src, andthat an unescaped-LIKE false positive (
_matching any character) does notreappear.
bun run typecheckclean. Fullbun testgreen except one pre-existing,unrelated failure:
src/cli/docs-parity.test.ts's CLI-crawl test timesout at its own 30s budget under load on this box (13-30 load average during
the run). Verified with a negative control — reproduces identically on
unfixed
main, same line, same ~30s mark. Not touched by this PR.What I did NOT check
_factory_srcrow (raisedelsewhere on the tracking task) — out of scope for a lookup-correctness fix;
this change makes a re-created mirror row harmless for name resolution
regardless of when it reappears.
conversations,instructions,todos— the other 3 of the "5 of 5packages tested" in the original diagnosis. This fix is at the shared
getRepo()/isDerivedCheckoutPathlayer, so it should cover themidentically, but I did not re-verify against their live indexed rows.
default_branchfield bug (open-todos,open-identitiesresolving to stale feature branches) — different failureshape, called out on the tracking task as a separate concern.
Fixes todos
c357a1f3.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.