Skip to content

[codex] make V1 migration org-idempotent#3783

Merged
saddlepaddle merged 1 commit intomainfrom
investigate-migration-blo
Apr 27, 2026
Merged

[codex] make V1 migration org-idempotent#3783
saddlepaddle merged 1 commit intomainfrom
investigate-migration-blo

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Apr 27, 2026

Summary

Make the V1 to V2 migration rerunnable when stale migration state exists for another organization.

Root Cause

The migration state table is scoped by organizationId, which is useful because V2 project and workspace IDs are organization-scoped. However, the migration also had a global preflight guard that queried for any successful project migration row belonging to a different org and aborted the run. That prevented the idempotent project reconciliation path from running, including findByPath linking to an existing V2 project.

Changes

  • Remove the fatal cross-organization migration guard from the V1 to V2 migration.
  • Remove the now-unused findMigrationByOtherOrg migration router endpoint.
  • Update migration tests so stale state from another org is ignored and the active org can migrate normally.
  • Add a completed-migration rerun regression test that verifies a second run does not create duplicate projects or workspaces.

Rerun Safety

The rerun path now relies on the existing per-row idempotency model:

  • Successful or linked project rows are reconciled with setupProjectImport and reported as synced instead of recreated.
  • Existing V2 projects discovered by repo path are linked instead of recreated.
  • Successful workspace rows are checked in the host service and reported as synced instead of re-adopted.
  • Previous project/workspace error rows are retried.
  • Previous recoverable workspace skips, including missing/stale worktrees and unresolved parents, are retried.
  • Sidebar migration writes remain guarded by deterministic IDs and collection.get(id) checks, so repeated runs do not duplicate sidebar entries.

Validation

  • bun test apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/writeSidebarState.test.ts apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/normalize.test.ts
  • bunx @biomejs/biome@2.4.2 check apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.ts apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/writeSidebarState.ts apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/writeSidebarState.test.ts apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/normalize.ts apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/normalize.test.ts apps/desktop/src/lib/trpc/routers/migration/index.ts
  • bun run --cwd apps/desktop typecheck
  • git diff --check

Summary by CodeRabbit

  • Bug Fixes

    • Migration now proceeds successfully even if v1 data was previously migrated in another organization.
    • Migration is now idempotent—rerunning the process won't create duplicate projects or workspaces.
  • Tests

    • Updated migration tests to cover additional scenarios and improve reliability verification.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

Removes the findMigrationByOtherOrg query and associated cross-organization migration state check that previously prevented v1 data migration if already migrated elsewhere. The migration function and tests are updated to reflect this behavior change.

Changes

Cohort / File(s) Summary
Migration Router
apps/desktop/src/lib/trpc/routers/migration/index.ts
Removed the public findMigrationByOtherOrg tRPC query that previously scanned v1 migration state for other organizations' project migrations with success/linked status.
Migration Implementation
apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.ts
Removed the cross-organization migration state query and error handling; initial concurrent fetch now limited to v1 data reads and listState only.
Migration Tests
apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts
Removed test expecting rejection on "other org" data existence; added test verifying migration succeeds when another organization owns the same project; added idempotency test confirming re-run detects already-imported entities and produces zero duplicates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A query was hopping away,
No more checking across the way,
One org, one path, clean and free,
Migration flows idempotently! ✨
Tests ensure we won't rebuild,
Already synced, our mission fulfilled.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title '[codex] make V1 migration org-idempotent' directly describes the main change: making V1 migration rerunnable/idempotent across organizations.
Description check ✅ Passed The description comprehensively covers all template sections: a clear Summary, Root Cause analysis, detailed Changes, Rerun Safety guarantees, and Validation commands with specific test runs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch investigate-migration-blo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@saddlepaddle saddlepaddle force-pushed the investigate-migration-blo branch from aa7748b to 66312fc Compare April 27, 2026 03:48
@saddlepaddle saddlepaddle marked this pull request as ready for review April 27, 2026 03:50
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

This PR removes the cross-organization preflight guard from the V1→V2 migration and its backing findMigrationByOtherOrg tRPC endpoint, making the migration rerunnable per-organization. Since listState was already scoped to organizationId and upsertState uses (organizationId, v1Id, kind) as its conflict key, the per-row idempotency model already provided the necessary isolation; the global guard was an overly conservative check that prevented legitimate reruns.

Confidence Score: 5/5

Safe to merge — changes are minimal, well-reasoned, and fully covered by the updated tests.

The guard removal is correct because listState was already org-scoped and upsertState uses a composite conflict key that includes organizationId. All remaining findings are P2 or lower. Two new tests cover both the cross-org isolation scenario and the completed-migration rerun path.

No files require special attention.

Important Files Changed

Filename Overview
apps/desktop/src/lib/trpc/routers/migration/index.ts Removes the findMigrationByOtherOrg query endpoint; the remaining router endpoints are unchanged and correct.
apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.ts Drops otherOrg from the parallel data-fetch and removes the guard that threw when another org had migration state; org isolation already enforced by listState's organizationId filter.
apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts Replaces the "other-org guard rejects" test with "other-org state does not block active org" and adds a comprehensive completed-migration rerun regression test.

Sequence Diagram

sequenceDiagram
    participant C as migrate.ts
    participant DB as migration router (tRPC)
    participant HS as hostService

    Note over C,DB: Before: guard would abort here if another org had success rows
    C->>DB: listState({ organizationId: ORG }) — org-scoped
    DB-->>C: rows for ORG only

    loop For each V1 project
        alt Row exists (success/linked) for ORG
            C->>HS: setupProjectImport(v2Id, repoPath) [idempotent]
            C-->>C: report "synced"
        else No existing row
            C->>HS: project.findByPath(repoPath)
            alt Candidate found
                C->>HS: setupProjectImport(candidate.id, repoPath)
                C->>DB: upsertState(status=linked)
                C-->>C: report "linked"
            else No candidate
                C->>HS: project.create(...)
                C->>DB: upsertState(status=success)
                C-->>C: report "created"
            end
        end
    end

    loop For each V1 workspace
        alt success row + workspace exists in host
            C-->>C: report "synced"
        else retry conditions met
            C->>HS: workspaceCreation.adopt(...)
            C->>DB: upsertState(status=success)
        end
    end
Loading

Reviews (1): Last reviewed commit: "make v1 migration org-idempotent" | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts (1)

587-616: Optional: strengthen multi-tenant fidelity of the other-org test.

The mock's state map is keyed by ${kind}:${v1Id}, so when the active org upserts project:p1, it overwrites the seeded some-other-org row. In production the migration_state table is org-scoped (compound key by org), so both rows should coexist after the migration. The current assertions correctly verify the active org migrates without being blocked, but they don't pin down the "doesn't disturb other org's state" invariant. If you want to lock that in, consider keying the mock by ${kind}:${v1Id}:${organizationId} (and updating listState/upsertState accordingly) and asserting the some-other-org row is still present after the run.

♻️ Sketch of a stricter assertion (requires composite-key mock)
 expect(summary.projectsCreated).toBe(1);
 expect(summary.errors).toHaveLength(0);
-expect(env.state.get("project:p1")?.organizationId).toBe(ORG);
-expect(env.state.get("project:p1")?.status).toBe("success");
+// Active-org row was created.
+expect(env.state.get(`project:p1:${ORG}`)?.status).toBe("success");
+// Other-org row is left untouched.
+expect(env.state.get("project:p1:some-other-org")?.v2Id).toBe(
+  "v2-other-org-project",
+);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts`
around lines 587 - 616, The test seeds a mocked migration state map keyed only
by "${kind}:${v1Id}" so an upsert from the active org overwrites the seeded
"some-other-org" row; update the mock to use a composite key
"${kind}:${v1Id}:${organizationId}" in makeFakeEnv (and adjust the mock
implementations of listState and upsertState to read/write using that composite
key) so rows for different organizations can coexist, then in the test
(migrate.test.ts) add an assertion after migrateV1DataToV2 that the original
"some-other-org" entry still exists and retains its organizationId/status while
the active org's entry is created/updated; reference the migrateV1DataToV2 call
and the mock helpers makeFakeEnv, listState, and upsertState when making these
changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts`:
- Around line 587-616: The test seeds a mocked migration state map keyed only by
"${kind}:${v1Id}" so an upsert from the active org overwrites the seeded
"some-other-org" row; update the mock to use a composite key
"${kind}:${v1Id}:${organizationId}" in makeFakeEnv (and adjust the mock
implementations of listState and upsertState to read/write using that composite
key) so rows for different organizations can coexist, then in the test
(migrate.test.ts) add an assertion after migrateV1DataToV2 that the original
"some-other-org" entry still exists and retains its organizationId/status while
the active org's entry is created/updated; reference the migrateV1DataToV2 call
and the mock helpers makeFakeEnv, listState, and upsertState when making these
changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 962df704-2c0c-484c-9d27-34fc755aa5a4

📥 Commits

Reviewing files that changed from the base of the PR and between 88e2500 and 66312fc.

📒 Files selected for processing (3)
  • apps/desktop/src/lib/trpc/routers/migration/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/lib/trpc/routers/migration/index.ts

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 3 files

@saddlepaddle saddlepaddle merged commit 16bcf96 into main Apr 27, 2026
7 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant