You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude/skills/resolve-pegasus-conflicts/SKILL.md
+26-5Lines changed: 26 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -24,10 +24,15 @@ First, determine what the user needs help with by checking their current state:
24
24
The user has already run `git merge <main branch>` and has conflicts to resolve. Help them resolve the conflicts using these strategies:
25
25
26
26
#### Database Migrations
27
-
-**Strategy**: Discard Pegasus migration changes, keep the user's changes
28
-
-**Reason**: Migration files should be regenerated, not merged
29
-
-**Action**: For conflicted migration files, accept theirs (the user's version from main), then run `./manage.py makemigrations` after all conflicts are resolved
30
-
-**Git command**: `git checkout --theirs <migration-file>` for each conflicted migration
27
+
28
+
**Principle**: When main/local and Pegasus disagree about a migration, main/local wins. Main's migration files typically reflect what the local DB (and prod) have already applied, so that history is the baseline you protect. When a Pegasus migration *conflicts with or duplicates* something main has already applied, you should drop Pegasus's version in favour of main's, then run `./manage.py makemigrations` to re-express any genuine change as a clean forward diff (the change lives in the merged models, so makemigrations re-derives it).
29
+
30
+
-**On a conflict**: For conflicted migration files, accept the user's version from main with `git checkout --theirs <migration-file>`, then run `./manage.py makemigrations` after all conflicts are resolved.
31
+
-**Watch for silent (conflict-free) migration changes — this is easy to miss**: Pegasus regenerates its own auto-generated migrations and sometimes **renames** them (e.g. `0002_customuser_language...` → `0002_customuser_customer...`). A rename is a delete-on-one-side + add-on-the-other, which git merges with **no conflict markers** — so it slips through silently and leaves you with a duplicate/competing migration the conflict step never flagged. After merging, always diff the migration dirs against main to catch this:
32
+
```
33
+
git diff --name-status main HEAD -- '*/migrations/*.py'
34
+
```
35
+
Treat any `R` (rename) the same as a conflict: restore main's file (`git checkout main -- <file>`), delete Pegasus's regenerated copy (`git rm <file>`), then `./manage.py makemigrations` to regenerate the forward diff cleanly. Brand-new app migrations (all `A`, e.g. a freshly added `ecommerce` app) are fine — keep those as-is.
31
36
-**djstripe migration references**: If djstripe was upgraded, check if app migrations reference old djstripe migrations that no longer exist (see the "djstripe 2.10 upgrade" section below).
@@ -75,12 +80,28 @@ After all conflicts are resolved and the merge is complete, run the verification
75
80
76
81
1.**Frontend install + build**: `npm install && npm run build`
77
82
2.**Python dependency sync**: `uv sync`
78
-
3.**Migrations**: `./manage.py makemigrations` then `./manage.py migrate`
83
+
3.**Migrations**: `./manage.py makemigrations` then `./manage.py migrate`. If `migrate` fails partway, see "Recovering from a partial migration" below.
79
84
4.**Tests**: `./manage.py test`
80
85
5.**Ruff format + lint**: `make ruff` (auto-formats and auto-fixes lint issues)
81
86
82
87
Commit any pending changes produced by the steps above (e.g. new migrations, formatting fixes) with a clear message. Docker users can substitute `make upgrade` for the build/migrate steps.
83
88
89
+
### Recovering from a partial migration
90
+
91
+
`migrate` commits each migration separately, so a failure partway through can leave earlier migrations applied while a later one fails — the DB is now in a half-migrated state that no longer matches its starting baseline. The local dev DB is typically kept at the same migration state as production, so it matters that you put it back. For Pegasus's schema-only, reversible migrations this is straightforward to undo.
92
+
93
+
1.**See what actually applied — check, don't theorize.** Surprising schema (tables/columns you didn't expect) is far more likely to be something *you just applied* than pre-existing cruft. Confirm with timestamps:
94
+
```
95
+
./manage.py dbshell -- -c "SELECT app, name, applied FROM django_migrations ORDER BY applied DESC LIMIT 20;"
96
+
```
97
+
The recent timestamps are what this run applied.
98
+
2.**Reverse them** back to the last good state, in reverse-dependency order (unapply the app that *depends on* another before the one it depends on):
99
+
```
100
+
./manage.py migrate <app> <last_good_migration> # or `zero` to unapply an app entirely
101
+
```
102
+
3.**If the reverse errors loudly** (e.g. `IrreversibleError` — a data migration with no reverse defined), stop and raise to a human rather than forcing it. Don't pre-gate on `atomic = False` or side effects; just attempt the reverse and let it fail loudly if it can't. (If a forward op already dropped data, that loss happened on apply — reversing won't recover it, and that's also a human escalation.)
103
+
4. Once back at a clean baseline, fix the offending migration (see "Database Migrations" above — usually a Pegasus rename/duplicate), then re-run `./manage.py migrate` and confirm the full chain applies cleanly from the baseline.
104
+
84
105
### Pushing
85
106
86
107
If everything above passed, the default is to push so the user can open a PR:
0 commit comments