Skip to content

fix: repair pnpm-lock.yaml broken by auto-merge collision - #212

Merged
ethanasm merged 2 commits into
mainfrom
claude/vpt-pr-automerge-ci-failures-2yqoqd
Aug 14, 2026
Merged

fix: repair pnpm-lock.yaml broken by auto-merge collision#212
ethanasm merged 2 commits into
mainfrom
claude/vpt-pr-automerge-ci-failures-2yqoqd

Conversation

@ethanasm

Copy link
Copy Markdown
Owner

Summary

  • Repairs pnpm-lock.yaml on main, which was left unparseable by the 2026-08-13 auto-merge burst: the Daily PR sweep routine squash-merged three dependabot PRs (chore(deps): bump mako from 1.3.12 to 1.4.1 #205, chore(deps): bump ruff from 0.16.1 to 0.16.2 #204, chore(deps): bump pip from 26.2 to 26.2.1 #202) within ~6 seconds, and the git merges textually collided in the lockfile, duplicating the ws@8.21.3 mapping key in both the packages and snapshots sections. pnpm install --frozen-lockfile fails with ERR_PNPM_BROKEN_LOCKFILE, so Web, Server, and SonarQube are all red on main. The duplicate blocks are removed, keeping the non-optional snapshot entry (ws@8.21.3 is a non-optional dependency of @expo/cli and isomorphic-ws).
  • Raises the pnpm.overrides nanoid pin from >=3.3.17 to >=3.3.18 for the new high advisory GHSA-2v37-7h3g-55p8 (reached via next > postcss > nanoid@3.3.17), so the pnpm audit --prod CI gate exits clean again. The remaining image-size pair stays covered by pnpm.auditConfig.ignoreCves.

The auto-merge routine itself is being updated separately (merges now require an up-to-date branch and serial merging with post-merge main CI verification) so green-on-a-stale-base merges can't corrupt the lockfile again.

Test plan

  • pnpm install --frozen-lockfile (the exact step failing on main) passes locally
  • Lockfile YAML parses; full duplicate-key scan over packages/snapshots sections is clean
  • uv.lock verified undamaged (parses, no duplicate packages; pip/ruff/mako/starlette bumps intact)
  • pnpm audit --prod exits 0 after the nanoid override bump
  • pnpm verify green end-to-end (build, lint, typecheck, coverage 96.7% API, audits)
  • CI green on this PR (Web / Server / Mobile / SonarQube)

Generated by Claude Code

claude added 2 commits August 14, 2026 05:22
Three dependabot PRs (#202, #204, #205) squash-merged within seconds on
2026-08-13 textually collided in pnpm-lock.yaml, duplicating the
ws@8.21.3 mapping key in both the packages and snapshots sections.
pnpm install --frozen-lockfile fails to parse the file
(ERR_PNPM_BROKEN_LOCKFILE), so every workflow on main is red.

Remove the duplicated blocks, keeping the non-optional snapshot entry
(ws@8.21.3 is a non-optional dependency of @expo/cli and
isomorphic-ws).
New high advisory (nanoid <3.3.18, infinite loop with size 0) reaches
prod via next > postcss > nanoid@3.3.17, unsatisfied by the previous
>=3.3.17 override pin. Raise the pin so pnpm audit --prod exits clean
again; the remaining image-size pair is already covered by
pnpm.auditConfig.ignoreCves.
@sonarqubecloud

Copy link
Copy Markdown

Copy link
Copy Markdown
Owner Author

Peer review (automated)

Verdict: approve with nits. The core repair is correct and I was able to verify it independently rather than by inspection alone — a pristine git archive HEAD copy regenerates a byte-identical pnpm-lock.yaml under pnpm install --lockfile-only, and pnpm install --frozen-lockfile (the exact step failing on main) succeeds. No P0 or P1 findings. The nits are all in the PR/commit prose, which is going to be the incident record for the follow-up automation fix, so they're worth a minute.

Verification performed

Check Result
Duplicate-key scan, whole file, all nesting levels (custom YAML loader — yaml.safe_load silently keeps last-wins, so parsing alone proves nothing) 0 dups at HEAD; base fd87d5c has exactly 2 (ws@8.21.3 at lines 10650 and 23457)
packagessnapshots cross-check (snapshot keys with peer-suffix stripped) 2103 packages / 2153 snapshots, 0 orphans in either direction
Dangling dependency references across all snapshots + importers 0 (5 apparent misses are npm-alias forms, e.g. string-width-cjs: string-width@4.2.3)
pnpm install --lockfile-only from clean git archive HEAD output byte-identical to the committed lockfile → the hand edit equals pnpm's own resolution, and package.json ↔ lockfile overrides: agree
pnpm install --frozen-lockfile --ignore-scripts passes, 1946 packages
pnpm audit --prod exit 0
uv.lock parses, 105 packages, no dup names; pip 26.2.1 / ruff 0.16.2 / mako 1.4.1 / starlette 1.6.0 all intact
All package.json + pyproject.toml parse; all four JS bumps survived the burst (@radix-ui/react-switch ^1.3.7, @radix-ui/react-dialog ^1.1.23, lucide-react-native ^1.31.0, expo ~57.0.11)

The load-bearing question: is ws@8.21.3: {} the right survivor?

Yes, and it matters. I built the non-optional reachability closure from the importers (dependencies + devDependencies, following only non-optional snapshot edges):

importer apps/mobile
  → expo@57.0.11(...)
    → @expo/cli@57.0.14(...)        # snapshot carries no `optional: true`
      → ws: 8.21.3                  # pnpm-lock.yaml:12440, under `dependencies:`, above the
                                    # `optionalDependencies:` block at :12442

ws@8.21.3 is reachable non-optionally, so pnpm-lock.yaml:23436 ws@8.21.3: {} is correct. Had the optional: true variant been kept instead, pnpm would skip ws for a non-optional consumer and @expo/cli (dev server / @expo/ws-tunnel) would break at runtime — this is not a cosmetic coin-flip. As a global sanity check, 0 snapshots marked optional: true are non-optionally reachable, i.e. the file's optionality marking is self-consistent after the edit.

The two deleted packages-section blocks were byte-identical (same sha512-201TZ/… integrity), so that half of the dedupe is a no-op either way.

nanoid override

Correct and exactly scoped. npm's bulk advisory endpoint confirms GHSA-2v37-7h3g-55p8 is vulnerable_versions: <3.3.18, high, CWE-835. After the bump: zero 3.3.17 references anywhere; postcss@8.5.25 and postcss@8.5.26 (pnpm-lock.yaml:21435, :21441) plus expo-router (:18356) all consume 3.3.18. The nanoid@<3.3.18 selector deliberately does not reach the nanoid@6.0.0 also in the tree — confirmed unaffected by the advisory, so no over-pin.


Findings

P0 — none

P1 — none

P2

1. The PR body and commit 1d4e676 name the wrong PRs as the colliding merges. — PR description; commit 1d4e676 message body

#202, #204, #205 are Python-only bumps touching pyproject.toml / uv.lock — they cannot collide in pnpm-lock.yaml. The four lockfile-touching merges in the burst were #211 (4317d79), #210 (27c1cf7), #209 (7a592f9), #208 (15b952e). Running the duplicate-key scan at each commit isolates it:

4317d79 (#211) dups: []
27c1cf7 (#210) dups: []
7a592f9 (#209) dups: []
15b952e (#208) dups: ['ws@8.21.3', 'ws@8.21.3']   ← introduced here

#208 is the expo-sdk group bump (946-line lockfile diff) and was squash-merged last of the four, onto a main that had already advanced through #211/#210/#209. That's a materially different failure signature than "three small PRs raced": the risk concentrates in large group bumps merged onto an advanced base. Recommendation: correct both the PR body and the commit body — this misattribution will otherwise land on main's history and steer the separate auto-merge fix at the wrong pattern.

2. isomorphic-ws is cited as a non-optional justification, but it isn't one.pnpm-lock.yaml:19335-19338; PR body + 1d4e676

  isomorphic-ws@5.0.0(ws@8.21.3):
    dependencies:
      ws: 8.21.3
    optional: true          # ← this path is optional

The conclusion is still right, but it rests entirely on the @expo/cli path. Recommendation: drop isomorphic-ws from the justification and cite expo → @expo/cli → ws instead, so a future reader re-deriving the decision doesn't start from a premise that the lockfile contradicts.

3. PR body ends with the forbidden session-link footer. — PR description, last line

Root CLAUDE.md § "Commit and PR Hygiene": "Do not include https://claude.ai/code/session_… URLs (or any other session-link footer) in commit messages or PR bodies." The body currently ends with _Generated by [Claude Code](https://claude.ai/code/session_01EmVSaay9zDGRKnN2r7yB4L)_. The two commit messages are clean — it's only the PR body. Recommendation: strip the line.

4. No structural guard stops git from silently text-merging a lockfile again. — repo root (no .gitattributes exists)

The PR notes the auto-merge routine is being fixed separately (up-to-date branch required + serial merging), which is the right primary fix. Defence-in-depth worth adding in the same follow-up: a .gitattributes marking generated lockfiles unmergeable, so git raises a visible conflict instead of producing a syntactically-plausible corrupt file:

pnpm-lock.yaml -merge
uv.lock        -merge

Note that no new CI check is needed to detect this class of break — pnpm install --frozen-lockfile already fails hard with ERR_PNPM_BROKEN_LOCKFILE. The gap was purely that the merges happened on stale bases where CI had already gone green. Worth stating that explicitly in the follow-up so nobody adds a redundant lockfile linter.

5. Test-plan wording: "pnpm audit --prod exits clean". — PR body, test plan

Accurate as to exit code (I measured 0), but the command still prints 2 vulnerabilities found / Severity: 2 high — the image-size pair suppressed via pnpm.auditConfig.ignoreCves (package.json:87-92) is counted in the summary line even though it's excluded from the advisory list and doesn't fail the gate. Recommendation: say "exits 0 (the 2 remaining highs are the ignored image-size pair)" so the next person reading CI logs doesn't re-open a closed question.

6. Consider recording how the hand edit was validated. — PR body, test plan

The test plan says the duplicate-key scan is clean, which is necessary but not sufficient — a hand-edited lockfile can be duplicate-free and still diverge from what pnpm would resolve. The strong check is that regenerating from the manifests reproduces the file exactly:

git archive HEAD | tar -x -C /tmp/lockcheck && cd /tmp/lockcheck
pnpm install --lockfile-only && diff pnpm-lock.yaml "$OLDPWD/pnpm-lock.yaml"   # identical

I ran this and it's identical, so nothing is blocked — it's just worth capturing in the body as the reason to trust a manual lockfile edit at all.


Scope check

Diff is package.json (1 line) + pnpm-lock.yaml (26 lines, 25 of them deletions) — no product code, no schema, no /v1/* surface, so the cross-platform parity checklist in CLAUDE.md doesn't apply. PR title fix: repair pnpm-lock.yaml broken by auto-merge collision is a valid conventional commit, under 70 chars, and correctly not feat: — so the mobile mobile-v* auto-bump stays a patch. All 9 CI checks on d246c77 are green (SonarCloud, both builds, all three audits, scan; e2e test skipped).

Nothing else in the burst appears damaged: uv.lock and every manifest parse cleanly and all eight bumps from the 2026-08-13 window are present at their intended versions.


Generated by Claude Code

@ethanasm
ethanasm merged commit dc61821 into main Aug 14, 2026
9 checks passed
@ethanasm
ethanasm deleted the claude/vpt-pr-automerge-ci-failures-2yqoqd branch August 14, 2026 16:17
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.

2 participants