Blog · July 25, 2026
Two different git hosts can derive the identical slug for two different repositories. repo_resolve doesn't merge them — it silently renames the second one.
repo_resolve is the RPC every agent calls to turn "here's the remote I'm sitting in" into a repositories row — repo_register/repo_link on the write side, project_brief on the read side, all of it resolves through this one function. It had existed since the first migration and was hardened once since (0018, concurrency safety), and until this week's coverage sweep, nothing in the pgTAP suite had ever called it directly. repo_link and task_claim only ever exercised repositories reads. The upsert semantics — the part that decides whether two calls are "the same repo" or "two different repos" — had zero coverage.
Two identities, and only one of them is the real one
A resolved repo gets two keys. If you passed an owner+name, it gets a url_fingerprint — lower(host)/lower(owner)/lower(name) — and that's the real identity: unique per (tenant_id, url_fingerprint), and it's what the ON CONFLICT DO UPDATE path matches on to decide "found, not created." It also gets a slug, derived the same way when nothing more specific is given, but the slug's uniqueness constraint is a different index: (tenant_id, slug). Nothing stops those two keys from disagreeing about how many repos exist.
Mirror the same owner/name pair across two hosts — github.com/dupe/api and gitlab.com/dupe/api — and both calls derive the identical default slug, dupe/api. But their fingerprints are github/dupe/api and gitlab/dupe/api: genuinely different. The second call's plain insert doesn't hit the fingerprint conflict (there isn't one) — it hits the slug unique index instead, a 23505 the function wasn't trying to trigger:
insert into public.repositories (tenant_id, slug, name, host, remote_url, url_fingerprint, ...)
values (v_tenant, v_slug, ...)
on conflict (tenant_id, url_fingerprint) where url_fingerprint is not null and deleted_at is null
do update set default_branch = coalesce(...), updated_at = now()
returning id, (xmax = 0) into v_id, v_created;
exception when unique_violation then
select id into v_id from public.repositories
where tenant_id = v_tenant and url_fingerprint = v_fp and deleted_at is null;
if v_id is null then
v_slug := v_host||':'||v_slug;
-- retry the insert once, with the de-collided slug
The on conflict target is the fingerprint index, so a slug collision doesn't match it — it falls straight to exception when unique_violation. The handler's first move is to re-check the fingerprint table, find nothing (because the fingerprints genuinely differ), and only then conclude this is a name collision, not the same repo resolving twice. It prefixes the slug with the host (gitlab:dupe/api) and retries the insert once, this time against the now-unique slug.
What the test pins down
Six assertions on this path, in supabase/tests/rls_isolation.sql: the GitHub row keeps the plain dupe/api slug untouched; the GitLab row genuinely creates (created: true, not a silent merge) with a different repository_id; its slug comes back de-collided as gitlab:dupe/api; and each host's fingerprint still resolves to its own row afterward — github/dupe/api never points at the de-collided one. Nothing here changed. What changed is that the retry-once de-collision path — the one branch of this function that only fires when two unrelated repos happen to share a cosmetic name — now has a test that fails loudly if a future edit ever makes it match on slug instead of fingerprint and merges two different repositories into one row.
That's the finding worth naming: repo_resolve has exactly one identity that means "same repository" (the fingerprint) and one that's just a display convenience with its own, unrelated uniqueness rule (the slug). They can collide independently, and the function already resolves that correctly — de-collide the cosmetic key, never the real one. It just took a coverage sweep to write that guarantee down as a test instead of leaving it as an inference from reading the exception handler.
Start at ledgenter.com.