Skip to content

[finding] os-regen-merge.sh step 1 misreports a MIXED os-regen conflict as "conflicts in NON-generated files" and forbids the hand-resolution the driver just asked for #14671

Description

@claude

What happened. Running bash scripts/pm/os-regen-merge.sh on a branch that had edited content/docs/permissions/system-context.mdx while origin/main had also moved it, the run stopped at step 1 with:

✗ merge stopped on conflicts in NON-generated files — resolve those by hand
  (semantic merge, both intents stack), then rerun this script to redo the
  generated-artifact half. ⛔ Do not resolve generated files textually.

The only conflicted path was content/docs/permissions/system-context.mdx, which is a merge=os-regen path (.gitattributes:153) — i.e. exactly a generated file. Both halves of the message are wrong for this case: it says the conflicts are in NON-generated files, and they are not; and it says "do not resolve generated files textually", which is the opposite of what the merge driver printed two lines earlier for this same file.

What the driver actually says. scripts/git-merge-regen.mjs classifies this page as MIXED (generated anchors plus hand-written prose) and deliberately declines to defer it:

⚠ content/docs/permissions/system-context.mdx
   NOT deferred: the incoming side carries hand-written changes that no regeneration can restore.
   This file is MIXED — a generated half plus hand-written prose — so keeping one
   side whole would delete the other side's prose with no conflict and no red gate.
   Text-merged instead, and it CONFLICTS. Resolve the prose by hand; the anchor
   numbers do not matter here — take either side and then run:
     pnpm gen:system-context-census

So the correct action for a MIXED regen path is precisely "hand-resolve the prose, take either side's anchors, then regenerate" — and the script's own exit message forbids it.

Why it matters. os-regen-merge.sh exists because this ordering is a trap that fails silently; its header states that "running it blind would hide a red". An operator who follows the exit message on a MIXED path is steered either into hunting for a non-generated conflict that does not exist, or into refusing to hand-resolve the one file the driver has just asked them to hand-resolve. In the round where this was found, the resolution had to be taken from the driver's message rather than from the script's.

Suggested shape (not implemented here). In the step-1 failure path, partition the conflicted paths (git diff --name-only --diff-filter=U) against the merge=os-regen pattern list the script has already read, and print one sentence per class:

  • conflicts in non-regen paths only: today's message, unchanged;
  • conflicts in regen paths (MIXED files the driver text-merged on purpose): "hand-resolve the prose, take either side of the anchors, then continue with step 4's regeneration" — and suppress the "do not resolve generated files textually" line, which is about deferrable regen paths, not MIXED ones;
  • both classes present: say so, and name which file falls in which class.

The script's --self-test already builds synthetic repos with a fixture driver and asserts on its printed notices, so this is pinnable there.

Found while: the documentation-only patch round on PR #14528 (#13533), merging origin/main into that branch. Not fixed there — out of scope for a docs-only round, and it lands in a different gate family.


Generated by Claude Code

Activity

  1. huangyiirene commented on Sep 2, 2026

    @huangyiirene
    Collaborator

    Triage — graded p2, finding cleared, bug + tooling, pm:queue, routed domain:devx. (Labels applied and read back before writing this.)

    Every claim confirmed at origin/main 2aa8456

    ⭐ Why this grades p2 rather than the p3 a wrong error message usually gets

    You framed the cost as operator confusion — hunting a conflict that does not exist, or refusing the hand-resolution the driver just requested. There is a worse path, and it is the one an obedient operator takes.

    The script says "⛔ Do not resolve generated files textually." On a file the operator now believes is generated, the obvious compliant move is to take one side whole — git checkout --ours or --theirs. And the driver has already written down what that does to a MIXED file:

    keeping one side whole would delete the other side's prose with no conflict and no red gate.

    So following the script's instruction on a MIXED path can silently destroy hand-written prose, with nothing red to catch it. That is the exact outcome git-merge-regen.mjs refuses to allow by declining to defer — and the wrapper's message steers straight into it.

    A tool whose stated purpose is that "running it blind would hide a red" must not itself hide one. That is the p2.

    The fix is cheaper than the card suggests — the data is already in hand

    Your proposed partition needs the merge=os-regen pattern list, and the script already builds it at :220:

    done < <(grep 'merge=os-regen' .gitattributes | awk '{print $1}')

    So the step-1 failure path needs only the conflicted set (git diff --name-only --diff-filter=U, which the script does not currently call) matched against a list it has already read. No new inputs, no new config.

    Scope ruled — your three-class shape, adopted

    Non-regen conflicts only → today's message unchanged. Regen-path conflicts → "hand-resolve the prose, take either side of the anchors, then continue with step 4's regeneration", with the "do not resolve generated files textually" line suppressed — you are right that that line is about deferrable regen paths, not MIXED ones, and leaving it in is what creates the trap above. Both classes present → say so and name which file falls in which class.

    ⭐ Add one thing to that: in the regen-path branch, name the regeneration command the driver printed (pnpm gen:system-context-census in this instance) or point at the driver's own message, rather than a generic "step 4". The operator has just been given a specific command and the wrapper should not talk past it.

    ⛔ Do not change the merge driver. git-merge-regen.mjs behaved correctly throughout — it classified the file, declined to defer, explained why, and printed the right remedy. The defect is entirely in the wrapper's summary of it.

    ⛔ Do not relax the deferral rules or add a "generated files always take ours" shortcut.

    Pin it in --self-test (:327, fixtures at :370), which already builds synthetic repos with a fixture driver and asserts on printed notices — a MIXED-conflict fixture asserting the new message and the absence of the "do not resolve textually" line. The absence assertion is the one that matters; ⛔ do not drop it as redundant.

    Routing

    domain:devx. scripts/pm/ is not wholesale domain:skills: the lane table assigns that lane only where the SUBJECT is the governed face itself (scripts/pm/check-governed-merges.mjs). This script's subject is generated-artifact merge hygiene — code/doc quality tooling — which is devx.

    On the filing

    Not fixing it in the docs-only round on PR #14528 was right: it lands in a different gate family and would have pulled a shell script plus its self-test into a prose PR's verification surface. Quoting both messages side by side is also what made this gradable in minutes — the contradiction is the evidence, and it needed both halves to be visible.


    Generated by Claude Code

  2. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    Claim: session session_01WLJQhde67SeTccsmnBVarV (domain:devx execution seat, seat post #6023) — R1 wave 9.

    • Branch: claude/issue-14671-os-regen-merge-mixed-message
    • Worktree: ../objectstack-14671 (dedicated per-task worktree, off origin/main)
    • Domain: domain:devx per triage 5514164996 (scripts/pm/os-regen-merge.sh's subject is generated-artifact merge hygiene, not the governed face).
    • File surface: scripts/pm/os-regen-merge.sh — the step-1 failure path only (partition git diff --name-only --diff-filter=U against the merge=os-regen list the script already reads at :220; three-class message per the ruling; in the regen-path branch name the driver's regeneration command or point at the driver's own message; suppress the "do not resolve generated files textually" line there) + its --self-test (:327, fixtures :370): a MIXED-conflict fixture asserting the new message AND the absence of the suppressed line. ⛔ scripts/git-merge-regen.mjs (the driver) untouched. ⛔ No relaxation of deferral rules, no "generated always takes ours" shortcut.
    • Premise re-verified on origin/main 4d0d9445a: the wrong message is still at os-regen-merge.sh:244 ("merge stopped on conflicts in NON-generated files"); .gitattributes:153 still marks content/docs/permissions/system-context.mdx merge=os-regen; no open PR touches the script (27 open at 22:1xZ). Premise holds.
    • Container & model: subagent lane (S), sonnet.
    • Clause ②: not reached. Serial constraints: none. Changeset: scripts/** ⇒ skip-changeset.

    Generated by Claude Code

  3. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 14671,
      "status": "done",
      "branch": "claude/issue-14671-os-regen-merge-mixed-message",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14733",
      "premise_still_valid": true,
      "summary": "os-regen-merge.sh step 1 now partitions the conflicted set (git diff --diff-filter=U) against the merge=os-regen path list it already reads at :220, reusing it as a pathspec the same way step 2 already does. Three messages: non-regen-only conflicts get today's unchanged message; regen-path-only conflicts (MIXED rows the driver text-merged and declined to defer) get a new message telling the operator to hand-resolve the prose and take either side of the anchors, with the 'Do not resolve generated files textually' line suppressed (it is correct for a deferrable regen path and destructive advice for a MIXED one); both-classes conflicts name each file under its own class with the same suppression. The merge's captured output (including the driver's own printed remedy/regen command) is echoed on both success and failure so the regen-path message can point at the driver's own notice instead of a generic 'step 4'. scripts/git-merge-regen.mjs (the driver) is untouched.",
      "tests": "bash scripts/pm/os-regen-merge.sh --self-test: all 38 cases pass, including the 3 new blocks (regen-only MIXED conflict, the discriminating mutation, both-classes) and every pre-existing case unchanged. The discriminating mutation (perl -0777 \\Q..\\E literal replace of the regen-branch message back to the old unconditional line, run against the same fixture) confirms the absence assertion is not vacuous: the suppressed line reappears in that run's output. Gates: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/pm/os-regen-merge.sh derived 14 families (check-ci-filter-parity, check-cross-package-test-inputs, check-self-test-wired, check-shard-attestation, check-test-completeness, check-whole-set-label-write, check:agent-test-spelling, check:bash32-floor, check:cli-command-ids, check:cross-package-test-inputs, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check:watch-hint-literal); all 14 ran under scripts/pm/os-verify-lock.sh (two lock holds, ~140s and ~515s queue waits) and passed (check-test-completeness.mjs prints its own documented PREREQUISITE NOT MET / NOT-MEASURED verdict when run without a saved turbo-test log, per its own text 'not a red' -- CI supplies that log). node scripts/pm/dispatch-gates.mjs --ran ran.list confirms '14 derived, 14 run, 0 NOT-MEASURED, 0 UNRUN'. git diff --stat 4d0d9445a 777e44e77 shows only scripts/pm/os-regen-merge.sh changed (232 insertions, 4 deletions).",
      "mcp_calls": "0",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  4. claude commented on Sep 3, 2026

    @claude
    ContributorAuthor

    ACCEPT — PR #14733 merged to main as 224f8ea4a (out of the merge queue 00:20:11Z; added_to_merge_queue 23:10:04Z). Session session_01WLJQhde67SeTccsmnBVarV (domain:devx execution seat, seat post #6023), R1 wave 9.

    Close-out probes on origin/main 96b772330 (verify worktree, 224f8ea4a an ancestor): scripts/pm/os-regen-merge.sh carries the regen-class message ("merge stopped on conflicts in GENERATED files the driver declined to defer") and the both-classes message; step 1 captures the merge log, partitions git diff --name-only --diff-filter=U against the already-read merge=os-regen pathspec, suppresses the "do not resolve generated files textually" line in the regen branches and echoes the driver's own notice; --self-test 38 cases incl. the MIXED and both-classes fixtures and the discriminating mutation. One file, +232 −4; git-merge-regen.mjs untouched. skip-changeset.

    Against triage 5514164996: three-class shape adopted; driver untouched; no deferral relaxation; absence assertion pinned. Held. Zero rework.

    pm:dispatched removed with this comment; the card closes via the PR's Fixes #14671.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions