Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
169 commits
Select commit Hold shift + click to select a range
c53b6f2
Edit CSV tables directly and remember column widths (#25442)
nwparker Oct 5, 2026
e46fa0d
test(linear): cover numeric-leading issue identifiers (#10241)
kenfdev Oct 5, 2026
67fc708
Group CSV editor modules and tests in their own folder (#25476)
nwparker Oct 5, 2026
1b957b7
fix: close worktree dialog before slow script checks (#25472)
nwparker Oct 5, 2026
c2c7649
fix(mobile): stop Android from selecting words while the chat transcr…
chokolademilch7 Oct 5, 2026
b332839
Remove completed SSH picker E2E plan (#24824)
kazooooo-ma Oct 5, 2026
40f9e7a
fix(codex): resume account switches without blanking the terminal (#2…
nwparker Oct 5, 2026
8b5de39
Submit initial OpenCode 2.0.12 prompts through the native startup plu…
nwparker Oct 5, 2026
fa180a9
feat(jira): pick the assignee when creating an issue — Automatic, me,…
kazuki-hanai Oct 5, 2026
1569e06
Reduce repeated terminal scans and unused Qoder test imports (#25425)
nwparker Oct 5, 2026
e2da3a1
fix: prevent IME confirmation from submitting text fields (#25480)
nwparker Oct 5, 2026
d173514
Update README downloads badge
github-actions[bot] Oct 5, 2026
2a68ea9
refactor(terminal): one commit module for terminal topology closes, w…
Jinwoo-H Oct 5, 2026
8f846d4
fix(native-chat): a Stop binds only the turn it actually stopped (#24…
brennanb2025 Oct 5, 2026
ad5aa01
fix(native-chat): a chat's finished-turn alerts come from the host re…
brennanb2025 Oct 5, 2026
98171a8
fix(native-chat): never delete chat history on read; a chat Orca can'…
brennanb2025 Oct 5, 2026
a68ee67
fix(codex): stop prompting a Codex restart when only its home changed…
OrcaWin Oct 5, 2026
d19e064
Fix legacy worker recovery snapshot timing for rollback safety (#25413)
AmethystLiang Oct 5, 2026
f250db5
fix(jira): read a missing or malformed Jira status as disconnected (#…
brennanb2025 Oct 5, 2026
761d63a
feat(agent-launch): keep long prompts off the launch line and paste t…
brennanb2025 Oct 5, 2026
b354aab
fix(mobile-chat): a failed message's text is added to the draft, and …
brennanb2025 Oct 5, 2026
ca2cdc0
feat(native-chat): answer /context in chat view for OpenClaude and OM…
brennanb2025 Oct 5, 2026
57fedee
fix(native-chat): the agent's exit ends its record, and an unconfirme…
brennanb2025 Oct 5, 2026
d9cc1d0
feat(activity): filters, matching header, row right-click menu, and s…
Jinwoo-H Oct 5, 2026
c5a1c0e
Add confirmed deletion of nested worktrees (#25668)
nwparker Oct 5, 2026
17ecee6
fix(relay): read reconcile's counter units after the cell lock (#25638)
Jinwoo-H Oct 5, 2026
d518d4a
Retire automatic profile JSON snapshots with safe recovery and downgr…
OrcaWin Oct 5, 2026
56dbf1b
fix(i18n): polish Japanese onboarding checklist copy (#19035)
tb-soshiro Oct 5, 2026
a42e586
fix(native-chat): keep a Stop's note with the turn it waited for and …
brennanb2025 Oct 5, 2026
e9ed75a
fix(native-chat): mark a chat whose start failed on its tab and works…
brennanb2025 Oct 5, 2026
a3bd6a6
fix: restore main's lint and localization checks after the nested-wor…
brennanb2025 Oct 5, 2026
f1ed355
feat(relay): drain pace window as a reviewed same-cap input, with dra…
Jinwoo-H Oct 5, 2026
e817b0e
refactor(native-chat): keep the provider resume handle opaque to shar…
brennanb2025 Oct 5, 2026
508419f
test: check structured-chat code for Electron imports even before the…
brennanb2025 Oct 5, 2026
7208be6
test(native-chat): cover paired runtime launch compatibility (#25072)
brennanb2025 Oct 5, 2026
d791421
Extract provider process supervision and stream reading from Codex (#…
brennanb2025 Oct 5, 2026
e2a1ecd
feat(activity): multi-select agent rows with a bulk right-click menu …
Jinwoo-H Oct 5, 2026
10bea3a
feat(relay): report lane service times, 503 causes, per-site hold p99…
Jinwoo-H Oct 5, 2026
990b0ed
fix(orchestration): give an OpenCode worker its task only once OpenCo…
OrcaWin Oct 5, 2026
ca883c5
fix(ci): unblock main's static analysis after the nested-worktree del…
brennanb2025 Oct 5, 2026
ab91559
feat(relay): one-command director deploy driver (#25642)
Jinwoo-H Oct 5, 2026
086b06c
fix(test): build the stop-note test's Codex provider handle with code…
Jinwoo-H Oct 5, 2026
9def4b9
feat(native-chat): publish where each submission was sent and the tur…
brennanb2025 Oct 5, 2026
3d7b306
fix(terminal): submit startup commands with Enter on every platform (…
AmoabaKelvin Oct 5, 2026
355e35b
perf(tab-bar): scrolling the tab strip no longer re-renders every tab…
AmoabaKelvin Oct 5, 2026
0e43c4f
fix(ai-vault): resume sessions whose worktree was deleted (#24236)
AmoabaKelvin Oct 5, 2026
1168e0f
fix(ssh): let a placed worktree seed while its host is in conflict (#…
AmoabaKelvin Oct 5, 2026
48f072a
fix(sidebar): remember whether the left sidebar is open across restar…
INatsukiI Oct 5, 2026
e65bbc9
fix(omp): stop the tab spinner sticking after subagents finish (#23662)
AmoabaKelvin Oct 5, 2026
1787585
fix(daemon): bump the terminal daemon protocol to v40 (#25291)
Jinwoo-H Oct 5, 2026
e43a9f7
fix(test): use the current provider handle shape in the submission-po…
brennanb2025 Oct 5, 2026
b01df81
test(ratchet): require src/main/provider-process now that it has land…
brennanb2025 Oct 5, 2026
13d94ac
feat(browser): add an eraser to screenshot markup (#24801)
AmoabaKelvin Oct 5, 2026
b6c5f3f
fix(native-chat): a sent image no longer flickers to its "Pasted imag…
brennanb2025 Oct 6, 2026
c6827fe
test: drop the duplicate codexProviderHandle import two main fixes bo…
brennanb2025 Oct 6, 2026
d3943c6
feat(editor): highlight Quarto and R Markdown files (#17772)
ykunisato Oct 6, 2026
9f56170
fix(i18n): fix garbled Japanese name suggestion and reversed ja/zh qu…
pinelibg Oct 6, 2026
135c92e
fix(native-chat): an unsent draft survives a reload or quit (#24905)
brennanb2025 Oct 6, 2026
9323443
fix(test): drop the duplicate codexProviderHandle import in the submi…
brennanb2025 Oct 6, 2026
41f1103
fix(test): restore the codexProviderHandle import in the submission-p…
Jinwoo-H Oct 6, 2026
dd39dcb
feat(orchestration): a native chat gets the orchestration pointer a C…
brennanb2025 Oct 6, 2026
ea6c9d6
fix(test): restore the codexProviderHandle import the #25078 squash r…
brennanb2025 Oct 6, 2026
9756dae
chore(i18n): translate 76 new keys to es/fr/ja/ko/zh (#25719)
AmethystLiang Oct 6, 2026
4ccfc16
fix(sidebar): preserve host filters across stale UI updates (#25737)
nwparker Oct 6, 2026
8127d94
test(agent-launch): keep the carry control on a prompt that still can…
brennanb2025 Oct 6, 2026
a47e0f5
fix(feedback): optimize oversized screenshots without losing detail (…
AmoabaKelvin Oct 6, 2026
08a970a
fix(native-chat): show the message rail from the first user message (…
brennanb2025 Oct 6, 2026
d73efcc
Reduce localization audit and relay setup work in CI (#25665)
nwparker Oct 6, 2026
1b52be6
fix(claude-accounts): preserve shared MCP OAuth credentials across an…
tomarai85 Oct 6, 2026
a259616
feat(pi): show Pi and OMP background children as rows (#23201)
AmoabaKelvin Oct 6, 2026
6012570
feat(files): reveal Source Control files and terminal file links in t…
AmoabaKelvin Oct 6, 2026
420447e
fix(agent-status): retire a removed worktree's hook-status rows (#23881)
AmoabaKelvin Oct 6, 2026
0e9e273
fix(relay): deploy driver builds the checked commit, prompts on their…
Jinwoo-H Oct 6, 2026
c7d74b1
feat(relay): give Asia cell c34 a promotion wave so it can become a g…
Jinwoo-H Oct 6, 2026
9b7c936
fix(dock): clear stale workspace unread counts (#24877)
AmoabaKelvin Oct 6, 2026
059e81a
chore(i18n): use 智能体 for Chinese Agent copy (#25767)
AmethystLiang Oct 6, 2026
ccc0bf7
Pause Pullfrog reviews while the CI runner queue recovers (#25760)
nwparker Oct 6, 2026
7f25413
Stage long agent launch lines where they are typed, so they arrive wh…
brennanb2025 Oct 6, 2026
db078b6
fix(claude): API-key Claude users are told they're not signed in (#25…
brennanb2025 Oct 6, 2026
3a03441
refactor(native-chat): structured agents declare their capabilities i…
brennanb2025 Oct 6, 2026
3285f82
Share the managed process lifecycle for structured providers (#25204)
brennanb2025 Oct 6, 2026
5f7c848
test(cross-version): load releases that import a package main dropped…
brennanb2025 Oct 6, 2026
3ee3a41
fix(markdown): strengthen dark table grid lines (#25653)
nwparker Oct 6, 2026
bfd1e9c
fix(codex): keep working status when Escape closes search or permissi…
nwparker Oct 6, 2026
b58f819
Soften native chat colors and code surfaces (#25651)
brennanb2025 Oct 6, 2026
f59194d
Bound file-search inventories and release abandoned Markdown data (#2…
nwparker Oct 6, 2026
661372d
Cancel abandoned file searches and prevent stale results (#25370)
nwparker Oct 6, 2026
e2ccf52
fix(remote-runtime): a paired terminal accepts input again after its …
Jinwoo-H Oct 6, 2026
c8ff7e8
Stop repeated recovery of finished workers (#25679)
nwparker Oct 6, 2026
fff8718
Improve Quick Open matching, file locations and recent history (#25371)
nwparker Oct 6, 2026
54ded3b
fix(relay): commit the cell counter in one round trip; cells boot wit…
Jinwoo-H Oct 6, 2026
8d2e926
fix(native-chat): ignore the terminal launch command when starting a …
brennanb2025 Oct 6, 2026
6c693ed
Show native chat tool calls as plain sentences (#25654)
brennanb2025 Oct 6, 2026
dc8b393
Add chat appearance settings: text size, code size, width (#25657)
brennanb2025 Oct 6, 2026
c0b07d3
Admit one provider event's journal writes as one queued operation, de…
brennanb2025 Oct 6, 2026
ca4e239
Remove low-value test inventories and duplicate fuzz oracles (#25791)
nwparker Oct 6, 2026
635e7f5
feat(github-projects): render Board project views as a kanban with dr…
NaoyaTatetsu Oct 6, 2026
020cebe
Add standalone Agent Client Protocol client layer (#24990)
brennanb2025 Oct 6, 2026
4e64fa9
fix(native-chat): start a Codex chat without waiting on the backgroun…
brennanb2025 Oct 6, 2026
f9c8cd4
Reduce redundant test coverage and unnecessary CI waits (#25806)
nwparker Oct 6, 2026
d7a9578
Add a shared timeline assembler for structured agent chats (not wired…
brennanb2025 Oct 6, 2026
eaaae01
feat(native-chat): record fresh sessions after failed restoration (#2…
brennanb2025 Oct 6, 2026
ac3a25c
Wait for Codex's live composer before pasting linked issue drafts (#2…
nwparker Oct 6, 2026
3ec030e
Update README downloads badge
github-actions[bot] Oct 6, 2026
1de3aa4
Fix truncated Unicode branch names in base ref search
nwparker Oct 6, 2026
29e6696
fix(codex): never write a Codex config.toml that Codex can't load, an…
Jinwoo-H Oct 6, 2026
8507111
Document Apple Git ref recovery retirement condition
nwparker Oct 6, 2026
6a9ba9d
Keep remote worktree creation from navigating paired clients (#25793)
nwparker Oct 6, 2026
41f293c
fix(notifications): replay completion sounds reliably (#15940)
DEOWL-kan Oct 6, 2026
c4ea14c
Show a tool call you stopped as interrupted, not failed (#25181)
brennanb2025 Oct 6, 2026
9905765
feat(native-chat): open structured chat's wire and stored records to …
brennanb2025 Oct 6, 2026
a2f197f
Refresh visible reviews automatically and stop settled merged polling…
nwparker Oct 6, 2026
0bcd49c
Let ACP connections own their agent processes (#25810)
brennanb2025 Oct 6, 2026
634c437
Fix premature Claude automation completion and inherited CI failures …
AmoabaKelvin Oct 6, 2026
f6f96db
Build SSH hostile-host Linux slots independently (#25821)
nwparker Oct 6, 2026
13ea359
Stop expensive checks when an unmerged PR closes (#25829)
nwparker Oct 6, 2026
670c59d
Translate ACP traffic into shared timeline events (#25090)
brennanb2025 Oct 6, 2026
ee91720
fix(native-chat): a chat you return to drops background tasks that fi…
brennanb2025 Oct 6, 2026
4de9f9f
Move orchestration capabilities out of protocol version registry (#25…
nwparker Oct 6, 2026
d9cf07d
Fix pinned workspace reveal expanding other hosts (#25836)
nwparker Oct 6, 2026
768c1ea
Add confirmed conversation rewind from structured chat messages (#19338)
brennanb2025 Oct 6, 2026
5b8a982
Fix private Linear images in task descriptions (#25849)
nwparker Oct 6, 2026
3ec38b8
Run Vitest on Bun with Node runtime contracts (#25840)
nwparker Oct 6, 2026
b752131
Add a Chat settings page for structured native chat (#25685)
brennanb2025 Oct 6, 2026
5cf3585
fix(native-chat): keep a message accepted before a quit or crash as a…
brennanb2025 Oct 6, 2026
7faa9f7
fix(native-chat): reasoning rows with a real open/finished state, and…
brennanb2025 Oct 6, 2026
cad8ea4
fix(tabs): stop tab and close tooltips firing while grazing the strip…
AmethystLiang Oct 6, 2026
842c6c6
fix(jira): tolerate malformed nested Jira status fields (#25656)
brennanb2025 Oct 6, 2026
ccab2a3
fix(native-chat): a long reply keeps its Working bar and Stop after a…
brennanb2025 Oct 6, 2026
468e4e1
fix(native-chat): a prompt card owns the chat input until its answer …
brennanb2025 Oct 6, 2026
f27ef42
fix(runtime): build the host terminal model when a phone opens an idl…
Jinwoo-H Oct 6, 2026
8d049b5
fix(codex): approve Orca's hook in managed Codex homes with Codex's o…
Jinwoo-H Oct 6, 2026
128d154
Fix Codex chat creation after its own model listing fails (#25754)
brennanb2025 Oct 6, 2026
e63e4f8
Update README downloads badge
github-actions[bot] Oct 6, 2026
defd85a
feat(floating-workspace): render the floating panel through the share…
brennanb2025 Oct 6, 2026
07981bd
fix(native-chat): chat copy that promised a retry, an update, or a co…
brennanb2025 Oct 6, 2026
61c9a3a
test(mobile): split the turn-disclosure test under the 800-line test …
brennanb2025 Oct 6, 2026
3268760
Add chat contrast and "Match terminal interface" (#25659)
brennanb2025 Oct 6, 2026
ab63890
fix(native-chat): start Windows chats without reading process creatio…
brennanb2025 Oct 6, 2026
975e732
feat(native-chat): a solid, fading Jump to latest (#25759)
brennanb2025 Oct 6, 2026
19661df
fix: stop Claude and agent helper processes when Orca quits or crashe…
brennanb2025 Oct 6, 2026
b922342
feat(native-chat): the chat reads "Stopping…" from your Stop until th…
brennanb2025 Oct 6, 2026
4c35516
fix(ci): restore main's typecheck and lint after #25751 and #24369 (#…
Jinwoo-H Oct 6, 2026
81a1968
feat(native-chat): attach several files from one picker selection (#2…
AmoabaKelvin Oct 6, 2026
a0a7535
fix(native-chat): say each chat outcome once and drop internal-only s…
brennanb2025 Oct 6, 2026
2f001fe
fix(remote-browser): host client browser pages again when a paired ho…
Jinwoo-H Oct 6, 2026
6a71732
Keep sidebar section collapse independent across execution hosts (#25…
nwparker Oct 6, 2026
ac8ea9f
fix(codex): write Orca's hook into ~/.codex only when something chang…
Jinwoo-H Oct 6, 2026
9e8a3a5
fix(repos): stop reporting clones that never ran as successful (#23770)
AmoabaKelvin Oct 6, 2026
83cf7cf
perf(ci): compile release JavaScript once for all packaging hosts (#2…
nwparker Oct 6, 2026
58b4633
refactor(codex): remove code the ~/.codex hook rework left unused (#2…
Jinwoo-H Oct 6, 2026
53edf8e
fix: recover deleted Active Server defaults and preserve editor hosts…
nwparker Oct 6, 2026
7e52bed
fix(editor): recover PDF previews after failed refreshes
nwparker Oct 6, 2026
d6b155d
Fix Claude's stuck spinner after Escape cancellation (#25846)
nwparker Oct 6, 2026
40f0319
fix(native-chat): fingerprint the consumed transcript boundary (#25965)
nwparker Oct 6, 2026
f7c542c
Keep profile saving alive after a stalled main loop (#25318)
AmethystLiang Oct 6, 2026
ce4c99a
fix(terminal): keep a pane closed just before quit from coming back o…
Jinwoo-H Oct 6, 2026
0a26f3e
fix(native-chat): honor saved agent Command and Arguments (#25721)
brennanb2025 Oct 6, 2026
e1e9d1c
feat(agent-launch): host-assigned caller identity and a launch record…
brennanb2025 Oct 6, 2026
28ed544
Fix send picker reveal under collapsed hosts and parents (#25950)
nwparker Oct 6, 2026
e813fa5
feat(native-chat): keep a message a Stop took back on screen, with on…
brennanb2025 Oct 6, 2026
6aa12c3
Name native Claude and Codex chats after their first message (#25724)
brennanb2025 Oct 6, 2026
51062e6
fix(test): pass resolveLaunchArgs in the record store slot test so ma…
Jinwoo-H Oct 6, 2026
cc60067
fix(test): pass resolveLaunchArgs in the Codex stopped-send-order tes…
Jinwoo-H Oct 6, 2026
f09e306
Merge remote-tracking branch 'upstream/main' into sync/upstream-1.4.214
frbarbre Oct 6, 2026
3be9710
docs(fork): log the sync of upstream 1.4.214 (163 commits)
frbarbre Oct 6, 2026
8dcfd90
Merge remote-tracking branch 'upstream/main' into sync/upstream-1.4.214
frbarbre Oct 6, 2026
19e9c9b
fix(fork): install Bun for the fork's tests in Verify, which upstream…
frbarbre Oct 6, 2026
976c05c
docs(fork): note the re-merge and Verify's Bun step in the 1.4.214 sy…
frbarbre Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
9 changes: 9 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
/skill-stubs/_shared/*.md text eol=lf
/skills/*/SKILL.md text eol=lf
/src/cli/bundled-skill-guides.ts text eol=lf
# SVG bytes feed embedded data URLs and asset hashes on every build host.
*.svg text eol=lf
# Compiler input line breaks must not depend on the checkout OS.
*.ts text eol=lf
*.tsx text eol=lf
*.html text eol=lf
# Bundled plugin trees are byte-hashed; CRLF checkout would break the pinned hash.
/resources/plugins/** text eol=lf
# Relay assets are copied verbatim into the bundle and hashed byte-for-byte into
Expand Down Expand Up @@ -88,3 +94,6 @@
/mobile/web-entry/**/*.otf -text
/mobile/web-entry/**/*.woff -text
/mobile/web-entry/**/*.woff2 -text

# Generated ACP schemas are checked byte-for-byte against formatted output.
/src/main/acp/generated/*.generated.ts linguist-generated=true text eol=lf
5 changes: 5 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ Thanks for contributing to Orca.

## Local Setup

Install Node 24, pnpm, and the Bun version in [`config/bun-version`](../config/bun-version).
`pnpm test` runs Vitest on Bun, with Node workers for runtime contracts such as SQLite,
native PTYs, socket liveness, and V8 memory behavior. `pnpm test:node` runs the same suites
entirely on Node. Builds and dependency installation still use Node and pnpm.

```bash
pnpm install
pnpm dev
Expand Down
69 changes: 68 additions & 1 deletion .github/workflows/ci-closed-pr-caches.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,86 @@
name: Clean closed PR caches
name: Clean closed PR work

on:
pull_request_target:
types: [closed]

permissions:
actions: write
pull-requests: read

jobs:
clean:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Cancel checks for an unmerged closed PR
if: github.event.pull_request.merged == false
uses: actions/github-script@v8
with:
script: |
const closed = context.payload.pull_request
const targets = [
['pr-checks', 'pr.yml'],
['node-server', 'node-server-tests.yml'],
['ssh-windows-hosts', 'ssh-windows-hosts.yml'],
['ssh-hostile-hosts', 'ssh-hostile-hosts.yml'],
['mobile', 'mobile.yml'],
['computer-e2e', 'computer-e2e.yml']
]
const stillClosed = async () => {
const { data: current } = await github.rest.pulls.get({
...context.repo, pull_number: closed.number
})
return current.state === 'closed' && current.merged_at === null &&
current.closed_at === closed.closed_at
}
if (!Number.isSafeInteger(closed.number) || closed.number <= 0 ||
!Number.isFinite(Date.parse(closed.closed_at))) {
throw new Error('Missing closed PR identity')
}
if (!await stillClosed()) return
for (const [prefix, workflow] of targets) {
const group = `${prefix}-${closed.number}`
let data
try {
const response = await github.request(
'GET /repos/{owner}/{repo}/actions/concurrency_groups/{concurrency_group_name}', {
...context.repo, concurrency_group_name: group,
headers: { 'X-GitHub-Api-Version': '2026-03-10' }
}
)
data = response.data
} catch (error) {
if (error.status === 404) continue
throw error
}
if (data.group_name !== group || !Array.isArray(data.group_members)) {
throw new Error(`Unexpected concurrency group: ${group}`)
}
for (const member of data.group_members) {
// Only whole PR runs; release/manual jobs never share this identity.
if (member.job_id !== undefined || !Number.isSafeInteger(member.run_id)) continue
const { data: run } = await github.rest.actions.getWorkflowRun({
...context.repo, run_id: member.run_id
})
if (run.event !== 'pull_request' || run.path !== `.github/workflows/${workflow}` ||
run.status === 'completed' ||
!Number.isFinite(Date.parse(run.created_at)) ||
Date.parse(run.created_at) > Date.parse(closed.closed_at)) continue
if (!await stillClosed()) return
try {
await github.rest.actions.cancelWorkflowRun({
...context.repo, run_id: member.run_id
})
core.info(`Requested cancellation of ${group}: ${member.run_id}`)
} catch (error) {
if (error.status !== 409) throw error
}
}
}
# No checkout: this runs trusted default-branch code, including for fork PRs.
- uses: actions/github-script@v8
if: '!cancelled()'
with:
script: |
const ref = `refs/pull/${context.payload.pull_request.number}/merge`
Expand Down
37 changes: 25 additions & 12 deletions .github/workflows/cloud-deploy-relay-production-same-cap-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:
expected-general-cells: { required: true, type: string }
expected-rehome-generation: { required: true, type: string }
wave-index: { required: true, type: string }
drain-pace-window-ms: { required: true, type: string }

permissions:
contents: read
Expand Down Expand Up @@ -48,14 +49,13 @@ jobs:
EXPECTED_GENERAL_CELLS: ${{ inputs.expected-general-cells }}
EXPECTED_REHOME_GENERATION: ${{ inputs.expected-rehome-generation }}
WAVE_INDEX: ${{ inputs.wave-index }}
# Each drained host re-dials /v1/assign and is re-placed through the director's
# single-slot sticky lane, which clears roughly 8 hosts/s fleet-wide. A 2,100-host
# cell over 2 min (~18/s) overran it on 2026-09-23 and browned out assign for every
# region for ~5 min. 5 min is the cell's maximum window (DRAIN_PACE_WINDOW_MAX_MS); a
# ~2,700-host cell still arrives at ~9/s, so expect a short tail, not zero rejects.
# The transition wait below is the 15-min migration lease plus this window. A cell on
# an older image drains at once.
DRAIN_PACE_WINDOW_MS: '300000'
# The cell spreads its drain sends over this window, so each drained host re-dials
# /v1/assign at hosts / window and is re-placed through the director's drain-return
# lane. The wave validator admits only its closed set, and anything below the 300000
# default only on US general cells: Asia drains are bound by their targets' accept
# rate. The transition wait below is the 15-min migration lease plus this window. A
# cell on an older image drains at once.
DRAIN_PACE_WINDOW_MS: ${{ inputs.drain-pace-window-ms }}
steps:
- name: Require exact reusable-workflow configuration
working-directory: .
Expand Down Expand Up @@ -104,10 +104,11 @@ jobs:

# One approved-cell table, in the wave validator the dispatch gate already uses, so
# a cell's class and its wave's selector delta cannot drift apart between the two.
# Also re-checks the drain pace against this cell, before anything reads production.
- name: Resolve this cell's admission class and wave selector delta
run: |
CELL_CLASS="$(node dev/scripts/relay-production-same-cap-wave.mjs cell-class \
--cell-id "${TARGET_CELL_ID}")"
--cell-id "${TARGET_CELL_ID}" --drain-pace-window-ms "${DRAIN_PACE_WINDOW_MS}")"
ENTRY_ADMISSION="$(jq -er '.entryAdmission' <<< "${CELL_CLASS}")"
SELECTOR_WAVE_DELTA="$(jq -er '.selectorWaveDelta' <<< "${CELL_CLASS}")"
if test "${DEPLOY_MODE}" = verify; then
Expand Down Expand Up @@ -537,10 +538,14 @@ jobs:
jq -e '.rollIsolated == true' <<< "${ISOLATE_RESULT}" >/dev/null
ISOLATE_GENERATION="$(jq -er '.generation' <<< "${ISOLATE_RESULT}")"
echo "SELECTOR_GENERATION_AFTER_ISOLATE=${ISOLATE_GENERATION}" >> "${GITHUB_ENV}"
node dev/scripts/prepare-relay-production-capacity-canary.mjs \
DRAIN_RESULT="$(node dev/scripts/prepare-relay-production-capacity-canary.mjs \
--director-origin "${DIRECTOR_ORIGIN}" --cell-origin "${CELL_ORIGIN}" \
--cell-id "${TARGET_CELL_ID}" --approved-cells same-cap --mode drain \
--pace-window-ms "${DRAIN_PACE_WINDOW_MS}"
--pace-window-ms "${DRAIN_PACE_WINDOW_MS}")"
echo "${DRAIN_RESULT}"
# What the cell accepted, which is 0 when an older image fell back to an unpaced drain.
echo "drain-applied-pace-window-ms=$(jq -er '.paceWindowMs' <<< "${DRAIN_RESULT}")" \
>> "${GITHUB_OUTPUT}"
# Restart-safe means an empty runtime for a whole pace window after the drain; leases
# held by unplaceable hosts are reported, not waited on (c28, 2026-10-01).
node dev/scripts/verify-relay-capacity-transition.mjs \
Expand All @@ -549,7 +554,9 @@ jobs:
--unobserved-bound "${EXPECTED_UNOBSERVED_BOUND}" \
--heartbeat either --admission migration-only --draining required \
--activity restart-safe --expected-image-digests "${PREDECESSOR_IMAGE_DIGEST}" \
--pace-window-ms "${DRAIN_PACE_WINDOW_MS}" --timeout-ms 1200000
--pace-window-ms "${DRAIN_PACE_WINDOW_MS}" \
--timeout-ms "$((900000 + DRAIN_PACE_WINDOW_MS))"
echo "drain-settled-at=$(date -u +%FT%TZ)" >> "${GITHUB_OUTPUT}"

- id: capacity-auth
if: ${{ inputs.mode != 'verify' }}
Expand Down Expand Up @@ -822,6 +829,8 @@ jobs:
timeout-minutes: 8
env:
DRAIN_STARTED_AT: ${{ steps.drain.outputs.drain-started-at }}
DRAIN_SETTLED_AT: ${{ steps.drain.outputs.drain-settled-at }}
DRAIN_APPLIED_PACE_WINDOW_MS: ${{ steps.drain.outputs.drain-applied-pace-window-ms }}
APPLY_STARTED_AT: ${{ steps.apply.outputs.apply-started-at }}
APPLY_COMPLETED_AT: ${{ steps.apply.outputs.apply-completed-at }}
VERIFY_ENDED_AT: ${{ steps.verify-target.outputs.verify-ended-at }}
Expand All @@ -838,6 +847,10 @@ jobs:
--apply-started-at "${APPLY_STARTED_AT}" \
--apply-completed-at "${APPLY_COMPLETED_AT}" \
--verify-ended-at "${VERIFY_ENDED_AT}" \
--drain-pace-window-ms "${DRAIN_PACE_WINDOW_MS}" \
--drain-applied-pace-window-ms "${DRAIN_APPLIED_PACE_WINDOW_MS}" \
--drain-settled-at "${DRAIN_SETTLED_AT}" \
--target-hosts "${TARGET_HOSTS:-}" \
--summary-file "${GITHUB_STEP_SUMMARY}" \
--output-file "${SHADOW_GATE_DIRECTORY}/${SHADOW_GATE_NAME}"

Expand Down
36 changes: 34 additions & 2 deletions .github/workflows/cloud-deploy-relay-production-same-cap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ on:
description: Exact durable regional-rehome control generation; it must be disabled
required: true
type: string
drain-pace-window-ms:
description: Window the cell spreads its drain over; below 300000 only for US general cells, named in the confirmation
required: true
default: '300000'
type: choice
options: ['300000', '60000', '30000']
canary-run-id:
description: Successful same-code canary in this rehome control generation; reusable across batches
required: false
Expand Down Expand Up @@ -103,11 +109,13 @@ jobs:
ROLLBACK_DIGEST: ${{ inputs.rollback-image-digest }}
CONFIRMATION: ${{ inputs.confirmation }}
CANARY_RUN_ID: ${{ inputs.canary-run-id }}
DRAIN_PACE_WINDOW_MS: ${{ inputs.drain-pace-window-ms }}
run: |
CELLS="$(node dev/scripts/relay-production-same-cap-wave.mjs validate \
--mode "${MODE}" --cell-ids "${CELL_IDS}" \
--target-digest "${TARGET_DIGEST}" --rollback-digest "${ROLLBACK_DIGEST}" \
--confirmation "${CONFIRMATION}" --canary-run-id "${CANARY_RUN_ID}")"
--confirmation "${CONFIRMATION}" --canary-run-id "${CANARY_RUN_ID}" \
--drain-pace-window-ms "${DRAIN_PACE_WINDOW_MS}")"
echo "cells=${CELLS}" >> "${GITHUB_OUTPUT}"
if [[ "${MODE}" =~ ^(canary-apply|batch-apply)$ ]]; then
echo 'job-mode=apply' >> "${GITHUB_OUTPUT}"
Expand All @@ -133,13 +141,15 @@ jobs:
ROLLBACK_DIGEST: ${{ inputs.rollback-image-digest }}
SELECTOR_GENERATION: ${{ inputs.expected-selector-generation }}
REHOME_GENERATION: ${{ inputs.expected-rehome-generation }}
DRAIN_PACE_WINDOW_MS: ${{ inputs.drain-pace-window-ms }}
run: |
node dev/scripts/relay-production-same-cap-wave.mjs verify-canary \
--file "${RUNNER_TEMP}/relay-same-cap-canary/authority.json" \
--commit-sha "${GITHUB_SHA}" --run-id "${CANARY_RUN_ID}" \
--cell-ids "${CELL_IDS}" \
--target-digest "${TARGET_DIGEST}" \
--rollback-digest "${ROLLBACK_DIGEST}" \
--drain-pace-window-ms "${DRAIN_PACE_WINDOW_MS}" \
--selector-generation "${SELECTOR_GENERATION}" \
--rehome-generation "${REHOME_GENERATION}"

Expand All @@ -158,6 +168,7 @@ jobs:
expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }}
expected-general-cells: ${{ inputs.expected-general-cells }}
expected-rehome-generation: ${{ inputs.expected-rehome-generation }}
drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }}
wave-index: '0'
secrets: inherit

Expand All @@ -177,6 +188,7 @@ jobs:
expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }}
expected-general-cells: ${{ inputs.expected-general-cells }}
expected-rehome-generation: ${{ inputs.expected-rehome-generation }}
drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }}
wave-index: '1'
secrets: inherit

Expand All @@ -196,6 +208,7 @@ jobs:
expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }}
expected-general-cells: ${{ inputs.expected-general-cells }}
expected-rehome-generation: ${{ inputs.expected-rehome-generation }}
drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }}
wave-index: '2'
secrets: inherit

Expand All @@ -215,6 +228,7 @@ jobs:
expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }}
expected-general-cells: ${{ inputs.expected-general-cells }}
expected-rehome-generation: ${{ inputs.expected-rehome-generation }}
drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }}
wave-index: '3'
secrets: inherit

Expand All @@ -234,6 +248,7 @@ jobs:
expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }}
expected-general-cells: ${{ inputs.expected-general-cells }}
expected-rehome-generation: ${{ inputs.expected-rehome-generation }}
drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }}
wave-index: '4'
secrets: inherit

Expand All @@ -253,6 +268,7 @@ jobs:
expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }}
expected-general-cells: ${{ inputs.expected-general-cells }}
expected-rehome-generation: ${{ inputs.expected-rehome-generation }}
drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }}
wave-index: '5'
secrets: inherit

Expand All @@ -272,6 +288,7 @@ jobs:
expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }}
expected-general-cells: ${{ inputs.expected-general-cells }}
expected-rehome-generation: ${{ inputs.expected-rehome-generation }}
drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }}
wave-index: '6'
secrets: inherit

Expand All @@ -291,6 +308,7 @@ jobs:
expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }}
expected-general-cells: ${{ inputs.expected-general-cells }}
expected-rehome-generation: ${{ inputs.expected-rehome-generation }}
drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }}
wave-index: '7'
secrets: inherit

Expand All @@ -310,6 +328,7 @@ jobs:
expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }}
expected-general-cells: ${{ inputs.expected-general-cells }}
expected-rehome-generation: ${{ inputs.expected-rehome-generation }}
drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }}
wave-index: '8'
secrets: inherit

Expand All @@ -329,6 +348,7 @@ jobs:
expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }}
expected-general-cells: ${{ inputs.expected-general-cells }}
expected-rehome-generation: ${{ inputs.expected-rehome-generation }}
drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }}
wave-index: '9'
secrets: inherit

Expand All @@ -343,14 +363,26 @@ jobs:
- uses: actions/setup-node@v4
with: { node-version: 24 }

# Its pace checks are what let a batch drain faster than the default; a missing artifact
# seals UNVERIFIED, which only the default accepts. Named from the gate's normalized cell,
# as cell_1 uploaded it, so a padded cell-ids input cannot miss it.
- name: Download the canary cell's shadow health verdict
continue-on-error: true
uses: actions/download-artifact@v4
with:
name: relay-same-cap-shadow-gate-${{ fromJSON(needs.gate.outputs.cells)[0] }}-${{ github.run_id }}.json
path: ${{ runner.temp }}/relay-same-cap-shadow-gate

- name: Seal exact successful canary authority
run: |
mkdir -p "${RUNNER_TEMP}/relay-same-cap-canary"
node dev/scripts/relay-production-same-cap-wave.mjs create-canary \
--cell-id "${{ inputs.cell-ids }}" \
--cell-id "${{ fromJSON(needs.gate.outputs.cells)[0] }}" \
--target-digest "${{ inputs.target-image-digest }}" \
--rollback-digest "${{ inputs.rollback-image-digest }}" \
--confirmation "${{ inputs.confirmation }}" \
--drain-pace-window-ms "${{ inputs.drain-pace-window-ms }}" \
--shadow-report "${RUNNER_TEMP}/relay-same-cap-shadow-gate/relay-same-cap-shadow-gate-${{ fromJSON(needs.gate.outputs.cells)[0] }}-${GITHUB_RUN_ID}.json" \
--commit-sha "${GITHUB_SHA}" --run-id "${GITHUB_RUN_ID}" \
--selector-generation "${{ inputs.expected-selector-generation }}" \
--rehome-generation "${{ inputs.expected-rehome-generation }}" \
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/cloud-operate-relay-asia-admission.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ on:
required: false
type: string
evidence-run-id:
description: Staging evidence run ID for C27, C27 canary run ID for C28/C29; C30-C33 take none and each proves itself by its own canary
description: Staging evidence run ID for C27, C27 canary run ID for C28/C29; C30-C34 take none and each proves itself by its own canary
required: false
type: string
evidence-run-attempt:
Expand Down Expand Up @@ -156,7 +156,7 @@ jobs:
evidence_kind=c27
artifact_name="relay-asia-c27-canary-${EVIDENCE_RUN_ID}-${EVIDENCE_RUN_ATTEMPT}"
;;
production-gce-c30|production-gce-c31)
production-gce-c30|production-gce-c31|production-gce-c34)
# No earlier proof binds a later cell's generation; its own canary below rolls it back on failure.
canary_cell="${TARGET_CELL_IDS}"
;;
Expand Down Expand Up @@ -330,7 +330,7 @@ jobs:
verify_cells=production-gce-c27,production-gce-c28,production-gce-c29
expected_states='{"production-gce-c27":"general","production-gce-c28":"migration-only","production-gce-c29":"migration-only"}'
;;
production-gce-c30|production-gce-c31|production-gce-c32|production-gce-c33)
production-gce-c30|production-gce-c31|production-gce-c32|production-gce-c33|production-gce-c34)
verify_cells="${CANARY_CELL}"
expected_states="{\"${CANARY_CELL}\":\"general\"}"
;;
Expand Down
Loading
Loading