Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion assets/land-fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ For each PR, requeue it unchanged if healthy (if the failure was flaky, name the
flaky test in its `failure` so it can be followed up), commit a
mechanical fix on its own branch, or hand it back with diagnosis and recommendation.
Allowed fixes: preserve both sides' intent in conflicts, format/lint, regenerate
lockfiles/generated files, or follow a base rename. Merge base where practical.
generated files, rebuild lockfiles as [Lockfiles](#lockfiles) says, or follow a
base rename. Merge base where practical.
flake.nix, flake.lock, shell.nix, default.nix, nix/, .envrc, .github/, .gitmodules,
and gitlinks (submodule entries) anywhere are protected. Merging the base may carry
the base's own changes to those paths: that is no edit of yours when they come out
Expand Down Expand Up @@ -79,6 +80,47 @@ A fixed head must be the tip of pr-<number>, descend from its pinned head, and
contain only that PR's fix and base history. Leave all other PR branches intact.
Keep the result file uncommitted.

## Lockfiles

When merging the base leaves a lockfile (`bun.lock`, `package-lock.json`,
`pnpm-lock.yaml`, `yarn.lock`, `Cargo.lock`, `uv.lock`, `poetry.lock`, `go.sum`,
and the like) conflicted or out of date with the merged manifests, rebuild it from
the base's copy, never from the PR's side: the base's copy carries dependency
changes the base made, such as a security bump, that the PR's copy would revert.

1. Resolve the manifests first (`package.json`, `Cargo.toml`, `pyproject.toml`,
`go.mod`, and the like), preserving both sides' intent.
2. Take the base's lockfile: `git checkout base -- <lockfile>`.
3. Run the package manager's minimal resolve against the merged manifests, such
as `bun install`, `npm install`, `cargo update --workspace` or a `cargo build`,
`uv lock`, or `go mod tidy`. Never run `cargo generate-lockfile`, `bun update`,
`uv lock --upgrade`, a `cargo update` without `--workspace`, or another update
that moves versions neither side asked for.
4. Reapply any lockfile change the PR made without a manifest change, such as a
`cargo update -p`, with a pinned targeted update such as
`cargo update -p <name> --precise <version>`. Where the base also moved that
package, keep the base's version if it is at or past the PR's.
5. Compare the result with the base's copy (`git diff base -- <lockfile>`) and
with the PR's own changes (`git diff base...pr-<number> -- <lockfile>`):
every lockfile change, and any manifest change the package manager made itself
(such as `go mod tidy` editing `go.mod`), must trace to a dependency or import
change in the PR.

Name the lockfiles you rebuilt in the reason. Hand back instead of taking either
side's copy, quoting the command's output or the offending diff in the diagnosis,
when:

- the package manager cannot rebuild the lockfile, or cannot reapply the PR's own
lockfile change with a pinned update;
- a change does not trace to the PR;
- the PR's manifest no longer admits a version the base moved to, such as a pin
below the base's security bump, or the base moved a package below the PR's
version: keeping either version is a behavior decision, not a mechanical fix.

`flake.lock` is a protected path. When the fix must edit it, as the protected-path
rule above allows, rebuild it the same way from the base's copy, with
`nix flake lock` as the minimal resolve.

## Flaky tests

A failure is flaky when it comes and goes with no cause in the PR or the base,
Expand Down
2 changes: 1 addition & 1 deletion quest/m0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ ownership-manifest design:

- [Land through the merge queue with an async fixer](/quest/m0/queue-watcher/README.md) - GitHub's merge queue lands one PR at a time, and a host watcher fixes and re-enqueues each PR it drops
- [Merge through a host gate](/quest/m0/host-land/README.md) - the host's fixer containers, and the frozen batched gate for the repositories not yet switched
- [The land fixer resolves every mechanical failure](/quest/m0/land-fixer/README.md) - lockfile merges, protected edits as ready commits, workflow pushes, and the PR's own CI as context
- [The land fixer resolves every mechanical failure](/quest/m0/land-fixer/README.md) - workflow pushes and the PR's own CI as context
- [Checks are judged by their latest run](/quest/m0/latest-check-runs.md) - `quest status`, `quest wait`, and the gate count each check's latest run on the head, as GitHub does, so a superseded cancelled or failed run stops failing them or refusing release-plz PRs GitHub would merge
- [Current landing status](/quest/m0/status-landing.md) - `quest status` clears a stale `landing-stopped` reason once the PR is queued again
- [Agents don't stall](/quest/m0/agent-stalls.md) - long waits block in the same turn, pre-existing failures don't stop review, and reviewer outages become waits
Expand Down
8 changes: 5 additions & 3 deletions quest/m0/land-fixer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,11 @@ Decision prompts, selected options marked ✅:
closed unmerged by the merge-queue switch below.
- Amended 2026-11-01 (maintainer, /quest-plan in moq-dev/moq): a fixer merge
on [moq-dev/moq#7318](https://github.com/moq-dev/moq/pull/7318) rebuilt
`bun.lock` from the PR's side and undid the base's advisory fix; see
[Lockfile merges](/quest/m0/land-fixer/lockfile.md).
`bun.lock` from the PR's side and undid the base's advisory fix.
[moq-dev/quest#410](https://github.com/moq-dev/quest/pull/410) gave
`assets/land-fix.md` a Lockfiles procedure: rebuild from the base's copy with
the package manager's minimal resolve, and hand back any change that does not
trace to the PR.

- Superseded 2026-11-01 (maintainer, /quest-plan; see the
[queue watcher](/quest/m0/queue-watcher/README.md)): repositories land
Expand All @@ -151,7 +154,6 @@ work, done once the Required quests below have merged.
## Required

- [Workflow permission](/quest/m0/land-fixer/workflows-permission.md) - waits on the maintainer to grant moq-bot `workflows`, so pushes touching workflows are accepted
- [Lockfile merges](/quest/m0/land-fixer/lockfile.md) - a conflicted lockfile is rebuilt from the base's copy, so a fix never reverts the base's dependency changes
- [PR CI as a signal](/quest/m0/land-fixer/pr-signal.md) - `/quest-merge` and the fixer read a PR's own CI without gating on it; public repos may run it, private ones need not

## Related
Expand Down
64 changes: 0 additions & 64 deletions quest/m0/land-fixer/lockfile.md

This file was deleted.

23 changes: 23 additions & 0 deletions test/land.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3275,6 +3275,29 @@ ${requeueScript}`);
])
expect(fixerPrompt).toContain(text);
});
test("rebuilds a conflicted lockfile from the base's copy", () => {
const prompt = fixerPrompt.replace(/\s+/g, " ");
expect(prompt).not.toContain("regenerate lockfiles");
for (const text of [
"rebuild lockfiles as [Lockfiles](#lockfiles) says",
"## Lockfiles",
"rebuild it from the base's copy, never from the PR's side",
"`git checkout base -- <lockfile>`",
"minimal resolve against the merged manifests",
"Never run `cargo generate-lockfile`",
"`cargo update -p <name> --precise <version>`",
"keep the base's version if it is at or past the PR's",
"must trace to a dependency or import change in the PR",
"Name the lockfiles you rebuilt in the reason.",
"Hand back instead of taking either side's copy",
"the package manager cannot rebuild the lockfile",
"a change does not trace to the PR",
"below the base's security bump",
"`flake.lock` is a protected path",
"rebuild it the same way from the base's copy, with `nix flake lock` as the minimal resolve",
])
expect(prompt).toContain(text);
});
test("has the fixer put its comments in the result instead of posting them", () => {
for (const text of [
"You have no GitHub credential and must not seek one.",
Expand Down