Skip to content

[finding] os-regen-merge.sh partitions by merge=os-regen ROUTING, not by generatedness — a generated-but-NOT_DRIVER_MANAGED path (migrations/registry.ts) lands in the "NON-generated, resolve by hand" bucket, which the same message forbids #18047

Description

@zhuangjianguo

Filed by the epic PM for #15939 (session_015c5G6TmpMKgnusmTpD7Ntt), 2026-09-13T15:30Z, at the maintainer's direction. ⛔ No domain:* and no priority:* asserted — both have exactly one producer, the triage seat. ⚠️ Triage's first touch is owed.

Surfaced live in PR #18016's round; verified here from source and from git check-attr.

Not a duplicate of #14671 — its fix left a third class

#14671 reported this same message misfiring on a MIXED merge=os-regen path, and was closed completed by merged PR #14733, "partition os-regen-merge.sh's step-1 conflict message by class". That fix is present and is what the script does today — a three-way partition into regen-only / non-regen-only / both.

⚠️ But #14671's own suggested shape said to "partition the conflicted paths against the merge=os-regen pattern list", and that is what was implemented. The partition key is routing, not generatedness — so the conflation was moved rather than removed, and a third class was never handled.

The mechanism, read from source

scripts/pm/os-regen-merge.sh:355-378:

done < <(git diff --name-only --diff-filter=U)                       # all_conflicts
done < <(git diff --name-only --diff-filter=U -- "${regen_paths[@]}") # regen_conflictsfor c in "${all_conflicts[@]}"; do
  case "$regen_set" in
    *"$NL$c$NL"*) ;;
    *) non_regen_conflicts+=("$c") ;;      # ← set difference, nothing asks "is it generated?"
  esac
done

if [ "${#regen_conflicts[@]}" -eq 0 ]; then
  echo "✗ merge stopped on conflicts in NON-generated files — resolve those by hand" >&2
  echo "  (semantic merge, both intents stack), then rerun this script to redo the" >&2
  echo "  generated-artifact half. ⛔ Do not resolve generated files textually." >&2

non_regen_conflicts is everything not routed to the driver. A generated path that is deliberately unrouted is labelled NON-generated and sent to a hand merge — while the third line of the same message forbids exactly that for generated files. The operator cannot satisfy both sentences, and nothing in the output tells them which one governs.

The path this bites, and the repo's own words for it

packages/spec/src/migrations/registry.ts. .gitattributes:32-34 describes it, unprompted, as exactly the unhandled class:

every conflicting case also conflicts in packages/spec/src/migrations/registry.tsgenerated, committed, unsharded, NOT_DRIVER_MANAGED — and every registration touches it by construction.

Measured with git's own attribute resolution, 2026-09-13T15:29Z:

path git check-attr merge
packages/spec/src/migrations/registry.ts unspecified ← generated, not routed
packages/spec/authorable-surface/system.json (lit control) os-regen
a fabricated path (dark control) unspecified

⚠️ Control limit, stated: the dark control returns the same value as the subject, because unspecified is the default for anything unmatched. It is the lit control that carries the reading — it proves routing resolves at all, so the subject's unspecified is a real "not routed" rather than a broken probe.

registry.ts is generated, is never routed, and "every registration touches it by construction" — so this is not an exotic path. Every ADR-0087 registration round can hit it.

The live instance

PR #18016 (#17785, the last card of the #15939 epic) was dequeued at 15:12:44Z with MERGE_CONFLICT after sibling PR #18007 merged ~193 lines into registry.ts. bash scripts/pm/os-regen-merge.sh exited 1 with the NON-generated … resolve those by hand message naming registry.ts.

The round did not follow it. It took a placeholder side purely to reach a committable state, committed the merge first per AGENTS.md §11, then regenerated and committed that as the discharge, citing check:migration-registry exit 0 as proof that registry.ts == f(entries/). Independently verified afterwards: the sibling's six registrations all survive at their pre-merge counts (2 / 2 / 6 / 6 / 2 / 14, lit controls 10 and 4, dark control 0).

⇒ the correct resolution was reached against the script's instruction, by an operator who knew the file was generated. One who trusted the message would have hand-merged a generated file — and .gitattributes:36 exists because that silently drops rows.

Suggested shape

Partition by generatedness, with routing as a sub-distinction, and print three cases rather than two:

  1. non-generated → today's message, unchanged.
  2. generated + routed, MIXED (the driver declined to defer) → today's MIXED message, unchanged — this is [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's fix and it is correct.
  3. generated + NOT routed (NOT_DRIVER_MANAGED) → "resolve by REGENERATION, never textually: take either side to reach a committable state, commit the merge, then run the generator and commit that. ⛔ Do not hand-merge."

The generated-but-unrouted set is already declared — .gitattributes names registry.ts in prose. Whether to make that machine-readable (a NOT_DRIVER_MANAGED marker the script can read, rather than prose) is the design call; ⛔ this seat is not making it.

⚠️ The script's --self-test already builds synthetic repos and asserts on printed notices, so class 3 is pinnable there — and #14671's fix was pinned the same way, which is why this limb needs its own fixture rather than trusting the existing ones.

Dedupe

search_issues ⇒ 16 results. Directly relevant:

Refs: scripts/pm/os-regen-merge.sh:355-378 · .gitattributes:32-34, :140-157 · #14671 / PR #14733 · #8360 · #17602 · PR #18016 (the live instance) · #15939

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions