-
Notifications
You must be signed in to change notification settings - Fork 1.1k
docs(devlog): record the 36-PR lane-stack merge, regression audit and UI screenshots #4463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| # 36-PR lane-stack merge into dev | ||
|
|
||
| ## Objective | ||
|
|
||
| Land every open lidge-jun-authored pull request on `dev` today, then close the | ||
| issues those PRs actually resolve. 36 PRs as of 2026-09-13, with `origin/dev` | ||
| at `7ca00ffe7c1299e80d650a3243b2bc7cf09109ad` when this roadmap was written. | ||
|
|
||
| Lane preparation is delegated to worktree-backed Codex threads. Merging is not: | ||
| the main session performs every merge itself and records maintainer integration | ||
| per MAINTAINERS.md. | ||
|
|
||
| ## Why lanes | ||
|
|
||
| A strictly sequential merge is not reachable in one day. Hosted CI costs 10-15 | ||
| minutes per pull request, so 36 serial gates alone exceed seven hours before any | ||
| conflict work. Lanes cut that two ways: lanes prepare in parallel, and within a | ||
| lane only the tip pays for CI. | ||
|
|
||
| Lanes are grouped by dominant file domain, not by disjoint file sets. An audit of | ||
| the live file lists found real cross-lane overlap in wave 1: S4 and S5 share | ||
| `src/server/auth-cors.ts`; S4 and S6 share `gui/src/api.ts`, | ||
| `src/server/index.ts`, and `src/server/ws-bridge.ts`; S4 and S2 share | ||
| `gui/src/pages/ApiKeys.tsx`, the API-keys workspace component, and | ||
| `tests/server/api-key-attribution.test.ts`; S5 and S6 share `src/config.ts` | ||
| and two config/policy test files. | ||
|
|
||
| That overlap does not break parallel preparation, but it does mean lanes cannot | ||
| be merged blindly. Lanes merge one at a time, and the next lane re-merges | ||
| `origin/dev` at its tip and re-runs its tip CI before it is merged. The | ||
| shrinking-diff property still holds inside a lane; across lanes the tip | ||
| re-merge is what absorbs the overlap. | ||
|
|
||
| ## CI economy | ||
|
|
||
| Each lane is a cumulative stack. The bottom branch merges `origin/dev`; each | ||
| branch above merges its parent's resulting commit. Every branch therefore | ||
| contains its ancestors, so merging bottom-up produces diffs that shrink as the | ||
| lane lands. | ||
|
Comment on lines
+36
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Restrict the bottom-up merge rule to applicable lanes.
🤖 Prompt for AI Agents |
||
|
|
||
| Every non-tip branch is pushed with `[skip ci]` in its head commit subject. | ||
| Only the lane tip runs hosted CI, and that tip run is the merge gate for the | ||
| whole lane. | ||
|
|
||
| This works because of how `ci.yml` is triggered. It declares `pull_request: {}` | ||
| plus a `push` trigger pinned to `[main, preview, dev]`. A push to a | ||
| `codex/*` branch never triggers it directly; the run comes from the | ||
| `pull_request` synchronize event, and GitHub suppresses `push` and | ||
| `pull_request` runs when the head commit subject carries `[skip ci]`. | ||
|
|
||
| Three workflows still run because they use `pull_request_target`, which | ||
| `[skip ci]` cannot suppress: `enforce-pr-target`, `pr-hygiene`, and | ||
| `pr-labeler`. All three complete in under a minute, so they cost nothing worth | ||
| optimizing, and they are the checks that keep the PR descriptions honest. | ||
| `react-doctor`, `service-lifecycle`, and `issue-quality-tests` use plain | ||
| `pull_request`, so they are suppressed along with `ci.yml`. | ||
|
|
||
| A non-tip pull request therefore reaches merge time with no `ci` aggregate | ||
| check, and that collides with a real policy line. MAINTAINERS.md states that | ||
| pull requests require successful required CI checks before merge, and the | ||
| maintainer-integration exception waives the second maintainer's approval, not | ||
| CI. `gh pr merge --squash --admin` will merge such a pull request mechanically, | ||
| but the mechanism is not the authorization. | ||
|
|
||
| The repository owner authorized this tip-only model explicitly for this batch, | ||
| so the deviation is a recorded owner decision rather than an inferred one. What | ||
| makes it defensible in substance is that the stack is cumulative: the content of | ||
| every branch beneath a tip is a strict subset of what the tip's green run | ||
| actually executed. The evidence exists; it is attached to the tip pull request | ||
| instead of to each branch. | ||
|
|
||
| Every merge record therefore names three things: the owner authorization for | ||
| tip-only CI, the tip pull request and run id that covers this branch, and the | ||
| fact that this branch's own `ci` check never ran. | ||
|
|
||
| Squash commit messages must never contain `[skip ci]`. The dev-branch run | ||
| triggered by each merge is the regression gate, and losing it would remove the | ||
| only signal that a landed lane broke `dev`. | ||
|
|
||
| ## Common principles | ||
|
|
||
| Pushes use `git push --no-verify`, fast-forward only. No `--force` anywhere. | ||
| Nothing is pushed to `dev`, `main`, or `preview`. | ||
|
|
||
| Local full-suite runs are forbidden. Allowed local checks are | ||
| `bun run typecheck`, `bun run structure:check`, `bun run privacy:scan`, and | ||
| `bun test` limited to the files a pull request touches. Hosted CI on the lane | ||
| tip is the only suite proof this goal accepts. | ||
|
|
||
| Each lane thread works in its own managed worktree and treats branches as | ||
| detached checkouts. Many of these branches are already checked out in other | ||
| worktrees, so a checkout by name is refused by git. | ||
|
|
||
| Conflicts are resolved by reading both sides and judging which matches current | ||
| behavior. A genuinely ambiguous conflict stops that link and is reported with | ||
| both sides and the reasoning, never guessed past. | ||
|
|
||
| Lane threads never merge, never mark a pull request ready, and never close | ||
| anything. They push and report. | ||
|
|
||
| ## Lane map | ||
|
|
||
| | Lane | Doc | Chain, bottom to top | Owner model | | ||
| | --- | --- | --- | --- | | ||
| | S4 audio | 010 | 4391, 4392, 4395 | xai/grok-4.6 | | ||
| | S5 providers | 010 | 4374, 4376, 4358, 4370 | xai/grok-4.6 | | ||
| | S6 singles | 010 | 4356, 4363, 4367, 4366, 4378, 4402, 4414, 4353, 4364 | xai/grok-4.6 | | ||
| | S2 accounts | 010 | 4375, 4404, 4408, 4361, 4369, 4401, 4357 | xai/grok-4.6 | | ||
| | S1 responses | 020 | 4346, 4354, 4345, 4355, 4359, 4351 | anthropic/claude-opus-5 | | ||
| | S3 trio+remote | 020 | 4427, 4433, 4441, 4362, 4372, 4373 | anthropic/claude-opus-5 | | ||
| | S7 global tip | 030 | 4334 | main session | | ||
| | Issue closure | 040 | n/a | kimi/k3[1m] research, main session executes | | ||
|
|
||
| S6 groups nine pull requests that share no files. They are chained only to | ||
| collapse nine CI runs into one; no ordering dependency exists between them. | ||
|
|
||
| S7 is alone and last. #4334 retires Codex Spark across 34 core files and | ||
| collides with nearly every other lane, so it rebases once against a fully | ||
| landed `dev` instead of fighting each lane in turn. | ||
|
|
||
| ## Work phases | ||
|
|
||
| wp1 is this roadmap. wp2 runs wave 1 (S4, S5, S6, S2), wp3 runs wave 2 (S1 then | ||
| S3), wp4 lands S7 and confirms the final dev regression run, and wp5 closes the | ||
| issues. Issue closure was added by the user after the goal was armed and | ||
| supersedes the original objective's exclusion of it. | ||
|
|
||
| ## Risks | ||
|
|
||
| The stale-base pattern from #4380 is expected to recur: failures on a lane tip | ||
| that belong to `dev` drift rather than to the lane. The tip CI run on the | ||
| integrated head is what distinguishes them, and a failure that survives | ||
| integration is real and fixed before that lane merges. | ||
|
|
||
| Lanes prepared in parallel go stale as earlier lanes land. Each lane thread | ||
| re-merges `origin/dev` at its tip immediately before its final report, and the | ||
| main session re-checks the tip run against the head it is about to merge. | ||
|
|
||
| A lane merged out of order defeats the shrinking-diff property and can strand a | ||
| child on a base that no longer exists. Lane order inside a chain is fixed; | ||
| only whole lanes are interleaved. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Merge topology discovered in flight | ||
|
|
||
| The first lane exposed a fact the roadmap had assumed away: not every pull | ||
| request in this batch targets `dev`. Seven of them are stacked children whose | ||
| base is another open pull request's head branch, which AGENTS.md sanctions as a | ||
| review workflow and `enforce-target` exempts from the wrong-base gate. | ||
|
|
||
| ## What that changes | ||
|
|
||
| Merging a stacked child does not land anything on `dev`. It squashes that | ||
| child's content into its **parent's branch**. Only a pull request whose base is | ||
| `dev` lands on `dev`. | ||
|
|
||
| The audio lane proved it. #4395's base was `codex/audio-streaming`, not `dev`. | ||
| Merging it produced `091e0c0a3` on that branch and left `dev` untouched. | ||
| The lane only landed once #4392, the `dev`-based root, was merged. | ||
|
|
||
| ## Live base map | ||
|
|
||
| | Child PR | Base branch | Parent PR | | ||
| | --- | --- | --- | | ||
| | #4404 | `codex/260912-60plus-accounts-history-identity` | #4375 | | ||
| | #4408 | `codex/260912-60plus-accounts-history` | #4404 | | ||
| | #4376 | `codex/260912-60plus-models-capabilities` | #4374 | | ||
| | #4373 | `codex/260912-60plus-operations-totals` | #4357 | | ||
| | #4372 | `codex/260912-60plus-remote-runtime` | #4362 | | ||
| | #4433 | `codex/260912-ws-stage-instrumentation` | #4427 | | ||
| | #4441 | `codex/260912-native-main-reauth-api` | #4433 | | ||
|
|
||
| Every other open pull request in the batch targets `dev` directly. | ||
|
|
||
| ## Corrected merge order | ||
|
|
||
| Inside a lane that contains a base chain, merge from the top down: the deepest | ||
| child first, so its content collapses into its parent's branch, then that parent, | ||
| and so on until the `dev`-based root is merged last. Each of those merges closes | ||
| one pull request, so the whole chain still reports `MERGED` rather than being | ||
| closed as superseded. | ||
|
|
||
| That inverts the roadmap's original bottom-up instruction for chained lanes. | ||
| Bottom-up still applies to lanes whose members all target `dev`, where each | ||
| merge is independent. | ||
|
|
||
| ## Cross-lane coupling | ||
|
|
||
| #4373 sits in the trio-remote lane but its base is #4357's branch, which the | ||
| roadmap put at the top of the accounts lane. The two lanes are therefore coupled | ||
| through that pair: #4373 must merge into `codex/260912-60plus-operations-totals` | ||
| before #4357 lands on `dev`. Treat #4357 and #4373 as one unit and merge them | ||
| together, after both lanes are otherwise ready. | ||
|
|
||
| ## Re-merge cost of squash | ||
|
|
||
| A squash merge rewrites the parent's commits into one, so a child that carried | ||
| those commits becomes conflicted the moment its parent lands. #4392 went | ||
| `DIRTY` immediately after #4391 squashed to `dev`. | ||
|
|
||
| The resolution is mechanical rather than a judgment call: `dev` gained only the | ||
| squashed form of content the child already carries, so every conflict is the | ||
| same change landing twice and the child's side is a strict superset. Resolve to | ||
| the child's side, then verify that nothing the parent introduced went missing | ||
| before pushing. | ||
|
Comment on lines
+58
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Make child-side re-merge resolution conditional.
🤖 Prompt for AI Agents |
||
|
|
||
| Doing the child merges top-down first, and only then landing the root, keeps | ||
| this to one re-merge per lane instead of one per link. | ||
|
|
||
| ## Audio lane result | ||
|
|
||
| | PR | Outcome | Commit | | ||
| | --- | --- | --- | | ||
| | #4391 | merged to `dev` | `afe987cff` | | ||
| | #4395 | merged into `codex/audio-streaming` | `091e0c0a3` | | ||
| | #4392 | merged to `dev`, carrying #4395 | `4a49d7f34` | | ||
|
|
||
| Hosted evidence: Cross-platform CI run 34731037202 `success` on stack tip | ||
| `ec9e3734e`. The dev regression run for the landing is 34731971757. The run for | ||
| #4391's intermediate landing was cancelled by the concurrency group when #4392 | ||
| pushed, which is expected and is not a failure. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # Merge mechanics settled in flight | ||
|
|
||
| Two mechanics changed after the audio lane landed, and a third was clarified. | ||
| They apply to every remaining lane. | ||
|
|
||
| ## Merge commit, not squash, for a lane tip | ||
|
|
||
| A squash merge collapses the branch into one new commit, so the ancestry of the | ||
| links beneath the tip is discarded. GitHub then has no way to see that #4346's | ||
| head is already in `dev`, and those pull requests stay open to be closed by | ||
| hand, reported as closed rather than merged. | ||
|
|
||
| A merge commit keeps the ancestry. Because each lane is cumulative — every link | ||
| merged its parent's commit — the tip's history contains every link's head. Once | ||
| that history reaches `dev`, GitHub marks each of those pull requests `MERGED` | ||
| on its own. One merge closes the whole lane with the correct status. | ||
|
|
||
| The repository already carries merge commits on `dev`, so this is not a new | ||
| shape in the history. | ||
|
|
||
| ## The ancestry invariant | ||
|
|
||
| The auto-close only works while the tip is a descendant of every link's current | ||
| remote head. Re-merging a lower link after the chain was built breaks it: the | ||
| link gets a new head that the tip has never seen. | ||
|
|
||
| That is not hypothetical. A live check found the responses, singles and | ||
| providers lanes intact, while accounts and trio-remote had every link missing | ||
| because both lanes re-merged their lower branches to absorb the audio landing | ||
| and never propagated upward. | ||
|
|
||
| Verify per link before merging a tip: | ||
|
|
||
| ``` | ||
| git merge-base --is-ancestor origin/<link-branch> <tip-commit> | ||
| ``` | ||
|
|
||
| A non-zero exit means that link will not auto-close. The repair is to propagate | ||
| upward — merge the refreshed lower link into the one above it, and carry that | ||
| result up to the tip. | ||
|
|
||
| ## Screenshot gate | ||
|
|
||
| `enforce-target` fails with `missing UI screenshot` when a pull request | ||
| mentions gui, which every cumulative tip carrying GUI work does. The owner's | ||
| decision is to satisfy it properly rather than bypass it: capture the UI change | ||
| and put the image in the tip's description. | ||
|
Comment on lines
+44
to
+47
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Reconcile the screenshot-gate procedure with the outcome record. This file says the owner decision was to satisfy 🤖 Prompt for AI Agents |
||
|
|
||
| A temporary Vite build is allowed for this — `bun run build:gui` or a build | ||
| inside `gui`, served locally for capture. Local full test suites remain | ||
| forbidden, so this permission is narrow: it covers building and viewing the | ||
| dashboard, not running `bun run test`. | ||
|
|
||
| Images need a hosted URL to render in a description, so the PNGs are committed | ||
| into the lane's devlog unit under `screenshots/` and referenced by raw link. | ||
| `devlog/_plan/260912_audio_apis_stack/screenshots/` is the existing precedent. | ||
| The screenshot commit rides along with the tip's final `origin/dev` re-merge so | ||
| the lane pays for one CI run rather than one per commit. | ||
|
|
||
| Where a lane's GUI diff has no visual delta — logic, types or hooks only — the | ||
| lane reports that instead, and the maintainer applies the repository's own | ||
| `gui-screenshot-waived` label with the reason recorded. That label is the | ||
| designed escape hatch and is applied by a maintainer, not by a lane. | ||
|
|
||
| ## Consequence for the roadmap | ||
|
|
||
| `010_wave1.md` and `020_wave2.md` describe `--squash` for lane merges. Read | ||
| that as `--merge` for a lane tip whose ancestry invariant holds, and as | ||
| `--squash` only for a pull request being landed alone. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the serial CI estimate.
Thirty-six runs at 10–15 minutes each take 6–9 hours. The current wording says the runs exceed seven hours across the full range, but the 10-minute case takes six hours. Use “6–9 hours” or state that seven hours is exceeded only near the upper end.
🤖 Prompt for AI Agents