Repository navigation
GET /api/codebases dedupes by its own URL normalization instead of remoteIdentity #4407
Description
Problem
GET /api/codebases removes duplicate projects before returning the list. It builds the dedupe key with its own normalizer, normalizeUrl = (url) => url.replace(/\.git$/, '') (packages/server/src/routes/api.ts:3411, used at :3419). Registration decides whether two URLs are the same remote with remoteIdentity (packages/core/src/services/project-registration-identity.ts:129-153). That function strips trailing slashes before .git, parses SCP-style SSH remotes and URLs into host/path, lowercases the host, and lowercases the path on github.com.
The two disagree today. Registration treats https://github.com/acme/api/, https://github.com/acme/api.git, git@github.com:acme/api.git and https://github.com/Acme/API as one remote. The listing treats them as up to four different projects. In the other direction, the listing can merge rows that registration would keep separate on a non-GitHub host, because it compares raw strings without parsing the host.
Why it is worth solving
Two sites that must agree on what counts as one remote are kept in agreement by discipline, and they already disagree. Registration refuses a second project on an already registered remote, so duplicate rows today come from legacy databases and from the update and repair paths. Those are the rows this dedupe exists to collapse, and its weaker key fails to collapse the common spelling variants.
Why now
Recent registration work (PR 4237) moved remote ownership onto remoteIdentity and its owner lookup in packages/core/src/services/registration-remote.ts. The listing's copy is now the only remaining hand-written remote-equality rule on this path.
Desired outcome
One definition of remote equality. Either the listing derives its key from the same identity registration uses, or the dedupe is removed if the implementer shows it no longer protects a reachable state. The implementer chooses which, and says why in the pull request.
Invariants
- Rows without
repository_urlare never collapsed, as today. - When rows are collapsed, the most recently updated one is kept, as today.
- No database schema change.
- The route's response shape is unchanged.
Acceptance
- Two codebases whose
repository_urlvalues differ only by a trailing slash, a.gitsuffix, or SSH vs HTTPS spelling of one GitHub remote appear once inGET /api/codebases, or the dedupe is gone with a stated reason. - A test exercises the listing with at least the trailing-slash and SSH/HTTPS spellings, and fails on the current
normalizeUrl. - No second remote-normalization function remains in
packages/server/src/routes/api.ts.
Found by Archon run 16f45747-248c-4419-b561-937e6a69772c and verified at c47d37a by the archon-discoveries workflow.
Current scope (maintainer ruling on the build queue's question on a stopped run, 2026-10-20)
GET /api/codebases must remain available when a stored local remote identity cannot be resolved. Retain each unresolved row separately, report the failure observably, and do not infer equality for it or introduce another remote-normalization rule. All existing issue invariants remain.
The maintainer's words: Keep the list available. One broken local remote must not hide every healthy project, and the operator needs the broken one in the list to find and fix it. Rows whose identity can't be resolved are returned as they are, not deduplicated, and each one logs a warning that names the codebase and the original error. No new response fields unless the plan already needs one.
Activity