Skip to content

fix(repo): remove the two files that conflict on every rebase - #3932

Merged
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
yazzang-homelab:fix/changelog-released-section-guard
Aug 6, 2026
Merged

fix(repo): remove the two files that conflict on every rebase#3932
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
yazzang-homelab:fix/changelog-released-section-guard

Conversation

@yazzang-homelab

@yazzang-homelab yazzang-homelab commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

What

Removes the two files that make a rebase onto dev fail for reasons unrelated to the change being rebased:

  1. Drops packages/*/CHANGELOG.md merge=union from .gitattributes.
  2. Untracks packages/coding-agent/src/internal-urls/docs-index.generated.ts (already in .gitignore) and adds two guards so it cannot come back.

Closes #3928. Closes #3929.

Why

merge=union never applied where it mattered

GitHub does not honour the driver when computing mergeability. Controlled A/B — same base 1f735b7b, same head (#3892 f18ae6ff), only the union line differing:

condition conflicts
union applied (repo config as-is) none
union line removed packages/ai/CHANGELOG.md, packages/coding-agent/CHANGELOG.md

GitHub REST reports mergeable: false / dirty for that head. Across all 24 open PRs the correlation is exact, no exceptions: every PR GitHub called dirty conflicts only on CHANGELOG once union is disabled; every PR it called clean conflicts under neither rule. So the driver bought nothing on GitHub and cost a phantom CONFLICTING badge on 15 PRs.

And where it did apply, it silently corrupted release notes

Union never conflicts — it concatenates both sides of an overlapping hunk. Release commits insert ## [X.Y.Z] - <date> directly beneath the surviving ## [Unreleased] heading (0e9e9447d), so a branch that added entries under Unreleased overlaps exactly that region.

Reproduced by union-merging #3697 into current dev:

28: ## [0.12.12] - 2026-08-05
...
61: - Deferred composer shell results now commit to the session transcript before ...

An unmerged PR's entry lands inside a version that already shipped. No conflict marker, no CI signal, invisible in the PR diff — it only exists in the rebase result.

Audit of dev (blame every released section; flag lines whose authoring commit is not an ancestor of the commit that created the section): 35 entries already sit in released sections across 5 packages. Worst case is packages/coding-agent/CHANGELOG.md L111–L112, authored b621997e on 2026-08-06, filed under ## [0.12.8] - 2026-08-02. Full breakdown in #3929; remediation is a separate change so this PR stays mechanical.

docs-index.generated.ts cannot be merged at all

The generator emits one line per doc, each holding an entire document as a single JSON string. Two edits to the same doc are a whole-line conflict with no three-way resolution.

It was already in .gitignore — and also tracked, which makes the ignore rule inert, because 23983ef4a added the ignore entry and the file to the index in the same commit (a commit about test hermeticity; its message never mentions either). So this restores the intent that was already recorded, it does not reverse a decision.

Impact: 6 of the 10 real conflicts among open PRs are this file, 4 PRs are blocked by it alone (#3918, #3868, #3788, #3741), and it churned in 31 commits in 30 days — including #3922/#3923/#3924, which are ACP and model changes.

Safety of untracking

  • Regeneration: root package.json prepare runs generate-docs-index. Verified bun install fires root prepare, including --frozen-lockfile. Every CI job that compiles or tests runs bun install --frozen-lockfile first; the three jobs that do not (affected-plan, affected-evidence-producer, affected) only run standalone scripts/ci-*.ts and never import the package.
  • Determinism: regenerating on a clean tree reproduces the previously committed file byte-for-byte.
  • Publishing: npm pack --dry-run ships the file in all three cases — no ignore entry, root .gitignore entry, and a package-local .gitignore entry — because the files allowlist outranks ignore rules. 1436 files packed in every case. prepack also regenerates it.

Guards added

.gitignore alone cannot prevent this recurring, since git only consults it for untracked paths. Two independent checks:

  • scripts/check-public-version-sync.ts gains a violation when the artifact is tracked, with the git rm --cached remedy in the message.
  • packages/coding-agent/test/docs-index-lazy.test.ts replaces its now-moot "committed index matches committed docs" case with "index stays untracked". The other four cases in that file already cover worktree-vs-docs parity and lazy loading, and are unchanged.

Both were verified non-vacuous by re-adding the file to the index and confirming each fails.

CONTRIBUTING.md gains a "Rebasing onto dev" section: CHANGELOG conflicts are now normal and are resolved by keeping both entries under ## [Unreleased], never by moving anything into a released section.

Testing

bun test scripts/check-public-version-sync.test.ts \
  packages/coding-agent/test/docs-index-lazy.test.ts \
  packages/coding-agent/test/bot-integration-docs.test.ts \
  packages/coding-agent/test/package-files.test.ts \
  packages/coding-agent/test/docs-utility-surface-cleanup.test.ts
# 34 pass, 0 fail

bun run check:tools                              # exit 0
bun --cwd=packages/coding-agent run check:types   # exit 0

No CHANGELOG entry: this changes contributor workflow and repository layout, not shipped behaviour — the published tarball is byte-equivalent. Adding one would also manufacture an instance of the conflict class this PR removes.

CI가 잡아낸 것 (수정 반영됨)

첫 푸시에서 Local public surfaces가 실패했다. public-site-sync.yml의 두 job은 bun install을 돌지 않아 (저장소 메타데이터만 읽으므로) 루트 prepare 훅이 발화하지 않고, 따라서 생성 파일이 없는 채로 check:public-sync가 "Generated docs index is missing"으로 떨어진다. 내가 dev-ci.yml만 조사하고 이 워크플로를 놓쳤다.

두 job 모두에 명시적 생성 단계를 추가했다. live-public-sync도 필요하다 — --livecheckLivePublicVersionSync에 더해 checkPublicVersionSync()를 무조건 호출하므로(스크립트 608행) 야간 스케줄도 같은 이유로 깨진다.

생성기는 node:fs/node:path/Bun.Glob만 쓰므로 node_modules 없이 돈다. 실패 커밋을 그대로 체크아웃해 재현하고 수정을 검증했다:

# install 없음, node_modules 없음
bun run check:public-sync
  -> Generated docs index is missing.            (exit 1, CI와 동일)

bun --cwd=packages/coding-agent run generate-docs-index
  -> Generated src/internal-urls/docs-index.generated.ts (120 docs)
bun run check:public-sync
  -> Public docs/site/version surfaces are in sync.   (exit 0)

이 실패는 이 PR이 없애려는 문제 자체의 사례이기도 하다: 추적되던 생성 파일이 "install 없이 도는 job"의 누락된 의존성을 가려주고 있었다.

GJC verdict

Self-approval is BLOCK; this needs an independent architect/critic/human verdict.

gajae.pr-review-verdict.v1 needs-human sha256:b4ac52bb2bf3e457bbb4a83540d1ee179d2380e2 reviewer:human evidence:local bun test (34 pass) + check:tools + check:types, all exit 0

  • Target branch is dev
  • bun check passes (check:tools, check:types)
  • Tested locally
  • CHANGELOG updated (if user-facing) — N/A, see above
  • Verdict above matches the exact PR head

Two artifacts made a rebase onto `dev` fail for reasons unrelated to the
change being rebased, and both are removable rather than manageable.

`packages/*/CHANGELOG.md merge=union` only ever applied locally. GitHub
does not honour the driver when it computes mergeability, so it reported
`dirty` on every PR that touched a CHANGELOG while git merged them
cleanly: 15 of 24 open PRs marked CONFLICTING merged with zero conflicts
under the repo's own attributes, and disabling the union line reproduced
GitHub's answer exactly (100% agreement, 24/24, no exceptions).

Worse than the phantom, union never conflicts -- it concatenates both
sides of an overlapping hunk. Release commits insert `## [X.Y.Z]`
directly beneath the surviving `## [Unreleased]` heading, so a branch
that added entries under Unreleased overlaps exactly that region and its
entries land *inside* the version that already shipped, with no marker
and nothing visible in the PR diff. Reproduced on Yeachan-Heo#3697: its entry
resolves under `## [0.12.12]`. An audit of `dev` finds 35 entries
already sitting in released sections across 5 packages, including one
authored 2026-08-06 filed under `## [0.12.8] - 2026-08-02`.

`docs-index.generated.ts` inlines each doc's full body onto a single
source line, so two edits to the same doc produce a whole-line conflict
git cannot three-way merge. It is already listed in .gitignore -- and
also tracked, which makes the ignore rule inert, because 23983ef added
both in one commit. 6 of the 10 real conflicts among open PRs are this
file; 4 PRs are blocked by it alone. It churned in 31 commits in 30 days.

Untracking is safe: the root `prepare` hook regenerates it on every
`bun install` (verified, including `--frozen-lockfile`), every CI job
that compiles or tests runs that install, and `npm pack` still ships it
because the `files` allowlist outranks ignore rules (verified three ways
-- 1436 files packed either way).

A real conflict an author resolves is strictly better than a silent
misfile, so no replacement merge driver is introduced.

Constraint: the published tarball must keep the generated docs index
Rejected: keep union, add a CI guard against released-section edits |
  leaves the phantom conflicts and the forced rebases in place
Rejected: emit one line per paragraph so union can merge | shrinks the
  conflict without removing it, and GitHub still disagrees
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test scripts/check-public-version-sync.test.ts
  packages/coding-agent/test/docs-index-lazy.test.ts
  packages/coding-agent/test/{bot-integration-docs,package-files,docs-utility-surface-cleanup}.test.ts
  (34 pass); both new guards verified non-vacuous by re-adding the file;
  bun run check:tools and packages/coding-agent check:types clean
Not-tested: a full release publish
@yazzang-homelab
yazzang-homelab force-pushed the fix/changelog-released-section-guard branch from 1b4f765 to b4ac52b Compare August 6, 2026 11:03
@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

dev(42f1091c)로 리베이스했다. 새 head b4ac52bb2bf3e457bbb4a83540d1ee179d2380e2.

리베이스 중 충돌이 하나 났는데, 하필 이 PR이 없애려는 바로 그 파일이었다 — dev가 docs-index.generated.ts를 다시 생성해 커밋했고(추적 중이니 매 머지마다 갱신된다) 이 브랜치는 그것을 삭제하므로 delete/modify 충돌이다. 삭제를 유지하는 것으로 해결했다. 이 PR이 왜 필요한지에 대한 추가 증거인 셈이다.

재검증: 34 pass / 0 fail (5개 파일), check:tools exit 0, check:types exit 0, 리베이스 후 작업트리 깨끗(생성 파일이 정상적으로 무시됨).

@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

현재 head b4ac52bb2bf3e457bbb4a83540d1ee179d2380e2 기준 MERGEABLE / CLEAN, 26개 체크 전부 초록, 6개 skip, 실패 0. 현재 dev(42f1091c)를 포함하므로 exact-head 게이트도 만족한다.

자체 승인은 BLOCK이라 verdict는 needs-human으로 두었다. @Yeachan-Heo 판정 부탁한다.

머지 후 효과: 열린 PR 중 docs-index.generated.ts 하나 때문에만 막혀 있던 4건(#3918, #3868, #3788, #3741)이 리베이스만으로 풀리고, CHANGELOG 유령 충돌 15건이 사라진다.

@Yeachan-Heo
Yeachan-Heo merged commit 8a3251c into Yeachan-Heo:dev Aug 6, 2026
32 checks passed
Yeachan-Heo pushed a commit that referenced this pull request Aug 6, 2026
Removing `packages/*/CHANGELOG.md merge=union` in #3932 was correct --
union never conflicts, it concatenates both sides of an overlapping hunk,
which silently filed entries into versions that had already shipped (35
such entries audited on dev, #3929). What it did not account for is the
transition: these files now conflict on rebase for the first time, and a
bad resolution drops the whole history with no marker.

That is not hypothetical. #3932 merged at 11:25:32Z. Between 11:29:29Z
and 11:35:02Z, ten open pull requests across six authors force-pushed
heads whose CHANGELOG was a single newline -- every released section
gone. #3920 #3697 #3870 #3908 #3887 #3864 #3729 #3869 #3866 #3873.
Nothing caught it: the files still parse, no test reads them, and the
loss looks like a large deletion inside an otherwise legitimate diff.

The guard asserts the one property that matters and nothing more: every
`## [X.Y.Z]` heading present at the merge base must still be present at
the head. Additions pass, rewording passes, and a release commit that
consumes `## [Unreleased]` into a new version passes. Only losing a
released section fails, and the message names the recovery command.

Runs in `affected-plan`, which already checks out full history and
carries the immutable event base sha, so it costs one bun invocation and
needs no new job.

Constraint: a release bump must still be able to add a version heading
Constraint: must not depend on byte-size heuristics -- a legitimately
  small changelog is not a violation
Rejected: threshold on deleted line count | fires on large legitimate
  edits and misses a small changelog emptied completely
Rejected: restore merge=union | reinstates the silent misfiling this
  replaced, and GitHub ignores the driver anyway
Confidence: high
Scope-risk: narrow
Reversibility: trivial
Tested: bun test scripts/changelog-history-guard.test.ts (11 pass);
  guard run against the three real broken heads (#3873 #3920 #3869)
  exits 1 and names the lost sections; clean range exits 0;
  bun run check:tools exit 0
Not-tested: a real release-bump PR end to end
Yeachan-Heo pushed a commit that referenced this pull request Aug 6, 2026
Removing `packages/*/CHANGELOG.md merge=union` in #3932 was correct --
union never conflicts, it concatenates both sides of an overlapping hunk,
which silently filed entries into versions that had already shipped (35
such entries audited on dev, #3929). What it did not account for is the
transition: these files now conflict on rebase for the first time, and a
bad resolution drops the whole history with no marker.

That is not hypothetical. #3932 merged at 11:25:32Z. Between 11:29:29Z
and 11:35:02Z, ten open pull requests across six authors force-pushed
heads whose CHANGELOG was a single newline -- every released section
gone. #3920 #3697 #3870 #3908 #3887 #3864 #3729 #3869 #3866 #3873.
Nothing caught it: the files still parse, no test reads them, and the
loss looks like a large deletion inside an otherwise legitimate diff.

The guard asserts the one property that matters and nothing more: every
`## [X.Y.Z]` heading present at the merge base must still be present at
the head. Additions pass, rewording passes, and a release commit that
consumes `## [Unreleased]` into a new version passes. Only losing a
released section fails, and the message names the recovery command.

Runs in `affected-plan`, which already checks out full history and
carries the immutable event base sha, so it costs one bun invocation and
needs no new job.

Constraint: a release bump must still be able to add a version heading
Constraint: must not depend on byte-size heuristics -- a legitimately
  small changelog is not a violation
Rejected: threshold on deleted line count | fires on large legitimate
  edits and misses a small changelog emptied completely
Rejected: restore merge=union | reinstates the silent misfiling this
  replaced, and GitHub ignores the driver anyway
Confidence: high
Scope-risk: narrow
Reversibility: trivial
Tested: bun test scripts/changelog-history-guard.test.ts (11 pass);
  guard run against the three real broken heads (#3873 #3920 #3869)
  exits 1 and names the lost sections; clean range exits 0;
  bun run check:tools exit 0
Not-tested: a real release-bump PR end to end
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