fix(github): migrate repo identity on a GitHub repository rename webhook - #5918
Conversation
Nothing in the codebase handled the GitHub App's `repository` webhook with `action: "renamed"` -- a repo rename (e.g. gittensory -> loopover) left every repo-identity-keyed row pointing at the old full_name until the next unrelated write happened to touch it, and any row keyed only by the old name (repositories, repository_settings) never updated at all without a webhook-driven path. Adds maybeHandleRepositoryRenamedWebhookEvent, wired into processGitHubWebhook right before the existing upsertRepositoryFromGitHub(payload.repository) call so that upsert UPDATEs the now-renamed anchor row instead of inserting a fresh, disconnected duplicate. renameRepositoryIdentity (src/db/repo-identity-rename.ts) migrates the structural identity columns for repositories, repository_settings, pull_requests, issues, and audit_events' target_key -- explicit per-table code (matching this codebase's house convention in repositories.ts) rather than a generic Drizzle helper, with a fold-on-collision pattern for the unique (repo_full_name, number) constraints. Also detects the one thing a rename can't migrate on its own: a self-host operator's container-private per-repo config folder is derived from the CURRENT repo name and is read-only from the app's perspective, so a folder that existed under the old name but not the new one means the operator's gate/autonomy/review policy silently reverted to defaults. maybeWarnOnMissingLocalConfigAfterRename surfaces that as an error-level audit event + log line instead of a routine info line, using the new hasLocalManifest() (focus-manifest-loader.ts). Scope is deliberately narrow to structural repo-identity columns -- review/audit tables (gate_outcomes, advisories, pull_request_reviews, etc.), caches/analytics tables, and REES/enrichment tables are tracked as separate follow-up PRs so each stays independently reviewable.
|
Caution 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🛑 LoopOver review result - fixes requiredReview updated: 2026-07-14 23:02:22 UTC
🛑 Suggested Action - Manual Review Review summary Nits — 5 non-blocking
Why this is blocked
📋 Copy for AI agents — paste into your coding agentCI checks failing
Review context
Contributor next steps
Signal definitions
[BETA] Chat with GittensoryAsk Gittensory a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://gittensory.aethereal.dev/docs/gittensory-commands 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
…bles Follow-up to the repo-rename webhook handler (JSONbored#5918): renameRepositoryIdentity only covered the anchor tables (repositories, repository_settings, pull_requests, issues, audit_events). Extends it to every review/audit table keyed on repo_full_name so a GitHub repository rename migrates their history too, instead of orphaning it under the old name: - gate_outcomes, active_review_tracking, pull_request_detail_sync_state, recent_merged_pull_requests: same fold-then-rename shape as pull_requests/issues -- each has a unique (repo_full_name, pull_number| number) index and an id that embeds the repo name. - pull_request_files: unique (repo_full_name, pull_number, path), a 3-column key -- folds per (pull_number, path) pair rather than a single-column IN, since row counts here are small and this avoids a raw composite-tuple SQL construct. - check_summaries: unique (repo_full_name, head_sha, name) -- same per-pair fold, but head_sha is nullable so the collision lookup branches on isNull vs eq per row (SQL NULL never equals NULL via `=`). - pull_request_reviews: no separate unique index, just the PK id (which embeds the repo name) -- folds on the id the rename would produce rather than a business-key tuple. - advisories: id is a random UUID (never repo-derived) with no unique constraint on repo columns, so this is a plain rename of repo_full_name plus the repo-embedded target_key, same LIKE+replace shape as audit_events.target_key. 30 new tests covering the rename, the collision-fold, an unrelated-row-untouched case, and (for check_summaries) the NULL head_sha fold specifically.
#5953) Follow-up to #5918/#5950: extends renameRepositoryIdentity to 14 more repo_full_name-keyed tables: - burden_forecasts, repo_queue_trend_snapshots, repo_sync_state: repo_full_name itself IS the primary key (single row per repo) -- same fold-then-rename shape as the anchor tables. - repo_sync_segments (unique on segment), contributor_repo_stats (unique on login), repo_labels (unique on name): each has an id that embeds the repo name and a unique index pairing repo_full_name with one other column -- folds on that column. - collision_edges: id embeds the repo name but is built in packages/loopover-engine and passed through verbatim, with no unique index -- folds on the id collision the rename would produce, same shape as pull_request_reviews. - notification_deliveries, github_agent_command_answers, repo_snapshots, repo_github_totals_snapshots, github_rate_limit_observations, product_usage_events, signal_snapshots: id is always a random UUID with no unique constraint tied to repo_full_name (several nullable) -- plain renames. notification_deliveries.deeplink and github_agent_command_answers.response_url are also rewritten, same as the anchor tables' own html_url treatment (their own canonical GitHub link, not incidental content). Deliberately excludes the request-scoped AI/LLM result caches (ai_review_cache, ai_slop_cache, linked_issue_satisfaction_cache, grounding_file_content_cache) and the RAG chunk cache (repo_chunks) -- every one is a rebuildable cache, not identity data: a miss after a rename just re-runs one LLM call or one re-index pass at the new name, which is graceful and self-healing, unlike an orphaned PR/issue/audit row a maintainer would otherwise need a GitHub API backfill to recover. Documented inline so the omission reads as deliberate, not forgotten. 24 new tests: a rename test for all 14 tables, plus a collision-fold regression test for the 7 that have a real fold path (three PK-is-repo_full_name tables, three single-column-unique-index tables, and the id-collision fold for collision_edges).
Follow-up to #5918/#5950/#5953: extends renameRepositoryIdentity to review_audit, contributor_gate_history, and submitter_stats -- raw-SQL- only REES/parity tables, never added to the Drizzle schema (see each migration's own header comment), so these three blocks use env.DB.prepare() directly instead of the query builder, matching how every real writer (parity-wire.ts, outcomes-wire.ts, contributor- calibration.ts, submitter-reputation.ts) already accesses them. - review_audit, contributor_gate_history: PK `id` alone, no separate unique index. `project` IS the repo full name at every live writer; `target_id` (`${project}#${pullNumber}`) and `id` both embed it. Fold on the id collision the rename would produce -- same PK-collision-only shape as pull_request_reviews/collision_edges. - submitter_stats: no id column -- PRIMARY KEY (project, submitter) directly. Fold on the other half of the composite key, `submitter`. Deliberately excludes two other raw-SQL REES tables: - review_targets: has NO live writer anywhere in this codebase (explicitly confirmed orphaned by src/review/public-stats.ts's own comment). Its `project` column is an agent/install-level slug that must NEVER be renamed (not a per-repo value), and its `repo` column's real on-disk semantics for that historical, one-time-bulk-copied data can't be independently verified from current code alone -- mutating identity columns on data no writer would ever repair carries real corruption risk for uncertain benefit. - repo_chunks: a rebuildable RAG chunk/embedding cache (same reasoning as the AI/LLM caches #5953 excluded), whose project/repo columns hold the bare owner and bare repo name SEPARATELY (not a single owner/repo string) and whose id is a lowercased, truncated-to-64-chars hash of the two -- a safe in-place string rename isn't mechanically available without risking a silently-corrupted truncated id. 7 new tests: a rename test for all 3 tables, a collision-fold regression test for each, plus an unrelated-row-untouched check for submitter_stats.
Summary
repositorywebhook withaction: "renamed"— a repo rename (e.g. gittensory → loopover) left every repo-identity-keyed row pointing at the oldfull_nameuntil an unrelated write happened to touch it, and rows keyed only by the old name (repositories,repository_settings) never updated at all without a webhook-driven path.maybeHandleRepositoryRenamedWebhookEvent, wired intoprocessGitHubWebhookright before the existingupsertRepositoryFromGitHub(payload.repository)call so that upsertUPDATEs the now-renamed anchor row instead of inserting a fresh, disconnected duplicate.renameRepositoryIdentity(src/db/repo-identity-rename.ts) migrates the structural identity columns forrepositories,repository_settings,pull_requests,issues, andaudit_events'starget_key— explicit per-table code (matching this codebase's house convention inrepositories.ts) rather than a generic Drizzle helper, with a fold-on-collision pattern for the unique(repo_full_name, number)constraints.maybeWarnOnMissingLocalConfigAfterRenamesurfaces that as an error-level audit event + log line instead of a routine info line, using the newhasLocalManifest()(focus-manifest-loader.ts).Scope
Deliberately narrow to structural repo-identity columns. Follow-up PRs (tracked separately) extend the same pattern to:
gate_outcomes,advisories,check_summaries,pull_request_files,pull_request_reviews, etc.)ai_review_cache,signal_snapshots,product_usage_events, etc.)review_targets,contributor_gate_history,review_audit,repo_chunks)Each stays independently reviewable rather than one large cross-cutting migration.
Test plan
npx vitest run test/unit/repo-identity-rename.test.ts test/unit/repo-rename-webhook.test.ts test/unit/focus-manifest-loader.test.ts— 52/52 passingnpm audit --audit-level=moderate— 0 vulnerabilitiesnpm run test:cicurrently fails at thetypecheckstep, but only on a pre-existing, unrelated issue onmain(stalegittensory-named exports intest/unit/miner-cross-repo-evaluation.test.tsfrom docs(miner): rename gittensory prose to loopover in miner/mcp packages #5899, unrelated to this PR's files) — already fixed in test(miner): fix stale gittensory identifiers in cross-repo-evaluation test #5913. Once that merges and this branch rebases, the full local gate is expected to be clean; this PR's own diff introduces zero new typecheck errors.