Skip to content

Gate the load-bearing dependencies behind a human merge - #2178

Draft
kriszyp wants to merge 4 commits into
mainfrom
kris/renovate-automerge-policy
Draft

Gate the load-bearing dependencies behind a human merge#2178
kriszyp wants to merge 4 commits into
mainfrom
kris/renovate-automerge-policy

Conversation

@kriszyp

@kriszyp kriszyp commented Aug 14, 2026

Copy link
Copy Markdown
Member

Majors, 0.x minors, the compiled addons, the at-rest/wire encoders and the SQL engine no longer automerge. Each is a case where a green suite is not evidence about the change: msgpackr v2 downgrade compatibility, the Node 26 Windows LTO leak, and 5.1.22 shipping rocksdb-js 2.4.0 while the cross-column-family read fix sat in 2.5.0.

They are also pulled out of the non-major group, so a bump that has to be read arrives in its own PR rather than inside a group of fifteen. rocksdb-js is disabled outright — .github/workflows/update-rocksdb-js.yml already opens a PR per upstream release, and Renovate would have produced a competing one for the same bump.

@harperfast/extended-iterable is on the list for a sharper reason than the rest: rocksdb-js requires it at exactly 1.0.3, so letting the root float admits a second copy of the module — and therefore a second SKIP sentinel, which surfaces as a phantom record from a vector query whose candidate was deleted. The companion pin PR narrows the range; this stops automerge widening it again.

This is the config half of a larger change. allow_auto_merge is now on for this repo and the Main ruleset requires ten real checks (unit + the six integration shards + validate/runLinter/format) instead of validate / validate alone, so "green" now means the suites actually ran.

For the human reviewer

  1. Carve-out scope. The human-merge list is the native addons, the at-rest/wire encoders, the SQL engine and node. It deliberately does not include every dependency that could break us — the criterion is "a green suite has repeatedly not been evidence", not "this is important". Adding names is free; the cost of the current line is that something outside it automerges. Reversible either way.
  2. 0.x blanket block. Renovate classifies 0.45.1 → 0.46.0 as minor, so the majors rule misses it, but by convention a 0.x minor is breaking. This blocks automerge for every 0.x dependency rather than only the load-bearing ones — the alternative is a shorter list and accepting that e.g. passport 0.7 → 0.8 (auth session handling) automerges. Chosen conservatively; one line to narrow.
  3. prConcurrentLimit 2 → 20. The carve-out creates PRs no bot will ever close, and at a limit of 2 a pair of them stops the group PR — security patches included — from being opened at all. 20 is headroom, not a measurement. Note branchConcurrentLimit defaults to it, so a busy Monday can open more parallel CI fan-outs than before.
  4. Two hand-kept name lists. The exclusion list and the deny list carry the same names with nothing tying them together. They agree here; if a future edit touches only the deny list, that dependency stays in the group and Renovate's group-branch automerge resolution applies the block to every non-major update. Safe direction, wide blast radius. Collapsing them looks possible via a later-rule groupName override, but I could not prove the semantics without a Renovate dry run and did not want to guess in the direction of "all automerge silently stops".

Verification

renovate-config-validator passes with zero migration warnings — the previous file needed one (excludePackageNames is deprecated and removed in Renovate v40; the negated matchPackageNames here is that migration). Not observable end-to-end short of a Renovate dry run: nothing in CI exercises grouping or automerge resolution, so the rule interactions rest on documented precedence, which the review checked independently.

The docker.io/node matcher is a fix found in review, not cosmetic: every Dockerfile says FROM docker.io/node:..., and a bare node in matchPackageNames is an exact match that never matched it.

Review coverage

Authored by Claude (Opus 5). Outside lenses: codex (graded leg), gemini via agy, and the harper-domain adjudicator. cursor-composer failed its artifact contract on this round and did not contribute; cursor-grok was pruned as the round's second Cursor lens. Four rounds; no correctness findings survived adjudication. The review found the two defects since fixed here — the inert node matcher and the 0.x gap — plus the competing rocksdb-js PR stream.

Human-Review-Need: 3 (decisions: carve-out-scope, concurrency-ceiling, zero-x-blanket, rocksdb-js-disabled, pin-vs-automerge) @ 361c578

@kriszyp
kriszyp requested a review from dawsontoth August 14, 2026 23:47

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates the Renovate configuration in renovate.json by increasing the concurrent PR limit and restructuring dependency grouping and automerge rules. Specifically, it excludes compiled addons, wire encoders, and the SQL engine from the automerged minor/patch group to ensure they are merged manually. The review feedback correctly points out that Renovate normalizes official Docker Hub images to include the library/ namespace, meaning docker.io/node is extracted as docker.io/library/node. To prevent this from bypassing the exclusions and being automerged, it is recommended to explicitly add docker.io/library/node (and its negated form) to the matchPackageNames lists.

Comment thread renovate.json
Comment on lines +40 to +41
"!node",
"!docker.io/node",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Renovate's Docker manager normalizes official Docker Hub images to include the library/ namespace. When parsing a Dockerfile with FROM docker.io/node:..., Renovate extracts the dependency name as docker.io/library/node rather than docker.io/node or node.

Because docker.io/library/node is not present in either list, it will bypass the exclusions and be grouped under 'all non-major dependencies' and automerged.

To prevent this, we should explicitly add docker.io/library/node (and its negated form) to both matchPackageNames lists (lines 41 and 73).

Suggested change
"!node",
"!docker.io/node",
"!node",
"!docker.io/node",
"!docker.io/library/node",

@claude

claude Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

One blocker remains: the docker.io/node matcher (renovate.json:42, 75) likely does not match Renovate's normalized docker.io/library/node packageName for the Dockerfiles' FROM docker.io/node:... base image, so a Node bump could still auto-merge instead of requiring a human — same bug class the PR already fixed for the bare node matcher. renovate.json is unchanged since this was raised (gemini-code-assist flagged the same line, comment 3787836354; my own inline comment 3787858715). This push only split the two unrelated test-flakiness fixes out into draft PR #2208, narrowing this PR to the Renovate policy change alone — the blocker itself is untouched.

Majors, 0.x minors (which Renovate still classifies as minor, and which convey
breaking changes by convention), the compiled addons, the at-rest/wire encoders
and the SQL engine no longer automerge. Each is a case where a green suite is
not evidence: msgpackr v2 downgrade compatibility, the Node 26 Windows LTO
leak, and 5.1.22 shipping rocksdb-js 2.4.0 when the cross-column-family read
fix was in 2.5.0.

They are also excluded from the non-major group, so a bump that must be read
does not arrive inside a group PR of fifteen others. That carve-out is what
raises prConcurrentLimit to 20: these PRs wait on a human, and at a limit of 2
a pair of them would stop the group PR — security patches included — from being
opened at all.

rocksdb-js is disabled outright. update-rocksdb-js.yml already opens a PR per
upstream release and its cleanup only closes PRs on its own head branch, so
Renovate would have produced a competing PR for the same bump.

Exclusions use matchPackageNames negation rather than the deprecated
excludePackageNames, and the node matchers name docker.io/node: a bare `node`
is an exact match and never matched the Dockerfile base image.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kriszyp
kriszyp force-pushed the kris/renovate-automerge-policy branch from 474b1cb to 361c578 Compare August 14, 2026 23:53
Comment thread renovate.json
"!ordered-binary",
"!structon",
"!node",
"!docker.io/node",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What: Renovate's Docker datasource normalizes official Docker Hub images to include the library/ namespace. FROM docker.io/node:... in the Dockerfiles is very likely tracked internally as packageName docker.io/library/node, not docker.io/node. Neither this exclusion list nor the human-merge block list (line 75) contains docker.io/library/node.

Why it matters: This is the same bug class this PR just fixed for the bare node matcher (an exact match that never matched docker.io/node:...). If the normalized name really is docker.io/library/node, the Node base image bump — the exact dependency the PR body calls out by name ("Node 26 Windows LTO leak") as the reason to require human merge — would still silently ride in the auto-merged group. gemini-code-assist flagged this as a high-priority inline finding on this PR (comment id 3787836354) with the same rationale and a concrete suggested diff; it has no reply or resolution yet.

Suggested fix: Add docker.io/library/node (and !docker.io/library/node in the exclusion list) alongside the existing docker.io/node entries in both lists — or confirm via a Renovate debug/dry-run log what packageName is actually resolved for this Dockerfile reference before merging.

Suggested change
"!docker.io/node",
"!node",
"!docker.io/node",
"!docker.io/library/node",

kriszyp and others added 2 commits August 15, 2026 06:11
Replace timing-dependent assertions that failed in the Node 24 and 26 CI matrix with condition waits for the observed committed state.

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Allow the cache propagation poll to continue through transient non-success responses on loaded CI runners.

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
The deflake changes now live in draft PR #2208, leaving #2178 limited to the Renovate policy change.

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
kriszyp added a commit that referenced this pull request Aug 19, 2026
Review follow-ups on the transaction commit-callback work, all raised as Low:

- Widen lastConfirmableEntry()'s contract comment: the walk skips two kinds of
  write, not one — explicit opt-outs and writes with no stored entry (a delete)
  — and pin the second with a put-then-delete case on an `audit: false` table.
- Drain staged completions in abort(). A completion staged but never aggregated
  by commit() previously failed silently (stageCompletion()'s no-op handler had
  marked it handled), and survived into a reused transaction's next commit().
- Release a still-live native handle in abort(). A write-only transaction takes
  no read reference, so the read-txn drain released nothing even though save()
  had created a handle.
- Retime the LMDB keyed-write ordering test on the conditional batch instead of
  a 50 ms timer, so a loaded runner cannot turn it into a silent false pass.
- Run the commit-callback rejection test on LMDB too, covering the no-op
  rejection handler in LMDBTransaction.doWrite.

Refs #2178

Co-Authored-By: Claude Opus <noreply@anthropic.com>
kriszyp added a commit that referenced this pull request Aug 19, 2026
A delete on an audited or delete-tracking table stores a null-value
tombstone (Table.ts: `if (audit || trackDeletes) updateRecord(id, null, …)`),
so getEntry() stays truthy and the confirmation walk stops there. Only a
delete on a table with neither is entry-less — which is the shape the
reloadMarker test builds. Comment-only; no behavior change.

Refs #2178

Co-Authored-By: Claude Opus <noreply@anthropic.com>
kriszyp added a commit that referenced this pull request Aug 19, 2026
Review follow-ups on the transaction commit-callback work, all raised as Low:

- Widen lastConfirmableEntry()'s contract comment: the walk skips two kinds of
  write, not one — explicit opt-outs and writes with no stored entry (a delete)
  — and pin the second with a put-then-delete case on an `audit: false` table.
- Drain staged completions in abort(). A completion staged but never aggregated
  by commit() previously failed silently (stageCompletion()'s no-op handler had
  marked it handled), and survived into a reused transaction's next commit().
- Release a still-live native handle in abort(). A write-only transaction takes
  no read reference, so the read-txn drain released nothing even though save()
  had created a handle.
- Retime the LMDB keyed-write ordering test on the conditional batch instead of
  a 50 ms timer, so a loaded runner cannot turn it into a silent false pass.
- Run the commit-callback rejection test on LMDB too, covering the no-op
  rejection handler in LMDBTransaction.doWrite.

Refs #2178

Co-Authored-By: Claude Opus <noreply@anthropic.com>
kriszyp added a commit that referenced this pull request Aug 19, 2026
A delete on an audited or delete-tracking table stores a null-value
tombstone (Table.ts: `if (audit || trackDeletes) updateRecord(id, null, …)`),
so getEntry() stays truthy and the confirmation walk stops there. Only a
delete on a table with neither is entry-less — which is the shape the
reloadMarker test builds. Comment-only; no behavior change.

Refs #2178

Co-Authored-By: Claude Opus <noreply@anthropic.com>
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