Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
685321e
chore(release): open dev at 2.65.0 before releasing 2.64.0 (#5666)
github-actions[bot] Sep 23, 2026
7dd1db2
fix(tests): bundle lane A — test hygiene, desktop restart guard and R…
lidge-jun Sep 23, 2026
8ffd323
fix(providers): bundle lane F1 — Cursor fast continuation, Meta Muse …
lidge-jun Sep 23, 2026
a1dba2c
fix(openai-chat): Pi developer role and duplicated serialized tool ca…
lidge-jun Sep 23, 2026
052e1d7
devlog: record the 2.64 release round (#5677)
lidge-jun Sep 23, 2026
aed3bb8
fix(responses): bundle lane E — combo resend safety, WebSocket replac…
lidge-jun Sep 23, 2026
c1905d4
fix(xai): bundle lane B — reasoning-model stop/penalty drops, policy …
lidge-jun Sep 23, 2026
7f8d538
fix(desktop,cli): bundle lane G — sidecar signing, restart warning, p…
lidge-jun Sep 23, 2026
9d1fa87
fix(claude): bundle lane C — routed windows with compact, picker desc…
lidge-jun Sep 23, 2026
aa2406b
fix(codex): bundle lane D — Codex home and WSL runtime discovery, int…
lidge-jun Sep 23, 2026
782bfb8
test(update): spell expected mise owner paths the way the detector re…
lidge-jun Sep 23, 2026
129406f
feat(claude): first-party Desktop Code tab model bindings (#5681)
lidge-jun Sep 24, 2026
bb003b7
docs(readme): put npm first and fold desktop downloads into small chi…
lidge-jun Sep 24, 2026
37f93da
docs(readme): show desktop chips openly and fold the full desktop sec…
lidge-jun Sep 24, 2026
0996ecb
fix: close four regressions from the 260923 bundle round (#5720)
lidge-jun Sep 24, 2026
560db33
fix(codex): keep the WSL home state list out of the module's dead zon…
lidge-jun Sep 24, 2026
6b7a91f
fix(cursor): route flat effort wire ids for opus 5.5 (#5723)
terrytan95 Sep 24, 2026
764dc13
fix(anthropic): handle Opus 5.5 forced tool choice (#5729)
lidge-jun Sep 24, 2026
3a301dc
fix(usage): price Cursor Claude Fast variants (#5730)
lidge-jun Sep 24, 2026
66d4cf9
feat(claude): gateway by default, first-party risk warning, and Deskt…
lidge-jun Sep 24, 2026
74cd423
feat(anthropic): keep Claude fast mode off until the provider opts in…
lidge-jun Sep 24, 2026
8b562bf
test(codex): share one Bun transpiler cache across write-lock childre…
lidge-jun Sep 24, 2026
359616e
test(claude): skip POSIX mode checks for picker files on Windows (#5734)
lidge-jun Sep 24, 2026
8cb20f7
fix(claude-intercept): picker CA trust that Desktop accepts, with IP …
lidge-jun Sep 24, 2026
be0b529
fix(openai-chat): read MiMo tool-call echoes without </function> or w…
lidge-jun Sep 24, 2026
742ee16
fix(desktop): make Cmd/Ctrl +/-/0 zoom the app window on every platfo…
lidge-jun Sep 24, 2026
df61bce
fix(responses): bundle L2 — Responses and streaming fixes (#5706, #56…
lidge-jun Sep 24, 2026
893c81c
fix(combos): bundle L1 — combo/failover safety (#5741)
lidge-jun Sep 24, 2026
e535c65
fix(ci): bundle L6 — re-attestation timestamps, event-driven sideband…
lidge-jun Sep 24, 2026
a8d526f
fix(providers): bundle L3 — MiMo, DeepSeek, Google, Command Code and …
lidge-jun Sep 24, 2026
dd7cb69
fix(desktop,gui): bundle L5 — macOS reopen, hidden-window polling, He…
lidge-jun Sep 24, 2026
6c171aa
fix(codex): bundle L4 — service uninstall key, startup rollout budget…
lidge-jun Sep 24, 2026
000abfc
test(server): stop the sideband ceiling proxy before afterEach remove…
lidge-jun Sep 24, 2026
1c4bd7d
fix(codex): keep native-main admission open across startup convergenc…
lidge-jun Sep 24, 2026
e1927bd
feat(desktop): spinner startup surface and first-launch GitHub star o…
devin-ai-integration[bot] Sep 24, 2026
608ed54
fix(subagents): preserve qualified roster ids (#5751)
luvs01 Sep 24, 2026
6104de0
feat(update): reliable update signal and a blue update dot on the tra…
lidge-jun Sep 24, 2026
9b21558
fix(responses): strip max_output_tokens only where the backend reject…
vadymhimself Sep 24, 2026
698435a
fix(usage): split the attempt row on account rotation, for every pool…
vadymhimself Sep 24, 2026
290ff76
fix(chat-completions): avoid injecting native main credential in pool…
rrmlima Sep 24, 2026
24b483b
fix(providers): fallback to transport destination for registry model …
leonclab Sep 24, 2026
13ecbdd
feat(provider): add a Claude Code CLI subscription provider (#5712)
localfoundry Sep 24, 2026
9cf3a06
fix(desktop): clone the tray app handle only where the update dot use…
lidge-jun Sep 24, 2026
45d3447
fix(ci): repair dev after the 2.65.0 round merges (#5768)
lidge-jun Sep 24, 2026
16ea244
Revert #5697 for the 2.65.0 release (#5770)
lidge-jun Sep 24, 2026
8755077
fix(tray): let Windows tray probes run when CODEX_HOME does not exist…
devin-ai-integration[bot] Sep 24, 2026
5cdd97e
test(windows): keep the badge tick budget off the offline health requ…
lidge-jun Sep 24, 2026
2747bae
release: promote the verified 2.65.0 preview tree to preview
lidge-jun Sep 24, 2026
0b6907c
release: prepare 2.65.0-preview.20260925 version metadata
lidge-jun Sep 24, 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
  •  
  •  
  •  
35 changes: 35 additions & 0 deletions .github/scripts/pr-quality-state.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,41 @@ describe("durable readiness re-attestation", () => {
assert.equal(result.pending.checkpointAt, null);
});

it("accepts a delayed author event when the live head and body remain unchanged", () => {
const pending = { version: 1, headSha: HEAD_A, baseRef: "dev", generation: 2, phase: "await-clear", checkpointAt: CHECKPOINT };
const result = advanceReattestation({
pending,
legacy: false,
current: true,
readiness: readiness(0),
live: live(body0, { updatedAt: "2026-09-22T09:00:01.000Z" }),
event: authorEdit(body0, body4),
});
assert.equal(result.pending.phase, "await-check");
assert.equal(result.pending.checkpointAt, null);
});

it("rejects future author events and missing or invalid live timestamps", () => {
const pending = { version: 1, headSha: HEAD_A, baseRef: "dev", generation: 2, phase: "await-clear", checkpointAt: CHECKPOINT };
for (const [name, liveUpdatedAt, eventUpdatedAt] of [
["future author event", LIVE_TIME, "2026-09-22T01:00:02.000Z"],
["missing live timestamp", undefined, LIVE_TIME],
["invalid live timestamp", "not-a-time", LIVE_TIME],
]) {
const result = advanceReattestation({
pending,
legacy: false,
current: true,
readiness: readiness(0),
live: live(body0, { updatedAt: liveUpdatedAt }),
event: authorEdit(body0, body4, { updatedAt: eventUpdatedAt }),
});
assert.equal(result.pending.phase, "await-clear", name);
assert.equal(result.changed, false, name);
assert.equal(result.canComplete, false, name);
}
});

it("rejects equal timestamps, title-only edits, and stale or reordered payloads", () => {
const pending = { version: 1, headSha: HEAD_A, baseRef: "dev", generation: 2, phase: "await-clear", checkpointAt: CHECKPOINT };
const cases = [
Expand Down
9 changes: 6 additions & 3 deletions .github/scripts/pr-readiness-reattest.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ function samePending(left, right) {
function qualifyingAuthorBodyEdit({ live, event, checkpointAt }) {
const checkpointMs = Date.parse(checkpointAt);
const eventMs = Date.parse(event?.updatedAt ?? "");
// GitHub can advance the live PR timestamp after the author event arrives.
// Do not cap the lag: a delayed event still proves this author's post-checkpoint
// edit when the exact body and head are unchanged at the live read.
const liveMs = Date.parse(live?.updatedAt ?? "");
return Boolean(
event?.name === "pull_request_target" &&
event.action === "edited" &&
Expand All @@ -123,9 +127,8 @@ function qualifyingAuthorBodyEdit({ live, event, checkpointAt }) {
event.headSha === live.headSha &&
typeof event.body === "string" && event.body === live.body &&
typeof event.previousBody === "string" && event.previousBody !== event.body &&
event.updatedAt === live.updatedAt &&
Number.isFinite(checkpointMs) && Number.isFinite(eventMs) &&
eventMs > checkpointMs
Number.isFinite(checkpointMs) && Number.isFinite(eventMs) && Number.isFinite(liveMs) &&
eventMs > checkpointMs && eventMs <= liveMs
);
}

Expand Down
124 changes: 116 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ jobs:
# step. A missing or malformed filter output must fail this job instead
# of silently making every expensive job skip.
ci: ${{ steps.scope.outputs.ci }}
desktop: ${{ steps.scope.outputs.desktop }}
native: ${{ steps.matrices.outputs.native }}
# Matrix include lists for keyring-smoke and npm-global-smoke, built and
# shape-checked by the same validation step as `native`.
Expand Down Expand Up @@ -265,6 +266,20 @@ jobs:
- '.github/workflows/ci.yml'
gui:
- 'gui/**'
# Building both Linux package formats and booting their real payloads is
# substantially heavier than the Rust-only desktop-shell check. Keep it
# scoped to inputs that can change the packaged shell, dashboard or
# standalone sidecar. The workflow names itself so edits to this lane
# cannot skip their own E2E.
desktop:
- 'desktop/**'
- 'gui/**'
- 'src/**'
- 'scripts/build-standalone.ts'
- 'scripts/standalone-targets.ts'
- 'package.json'
- 'bun.lock'
- '.github/workflows/ci.yml'
# The docs site is built by nothing else on a pull request. `ci` above
# deliberately omits `docs-site/**` -- a prose edit has no business
# starting the cross-platform suite -- and `deploy-docs.yml` triggers
Expand Down Expand Up @@ -342,6 +357,7 @@ jobs:
shell: bash
env:
CI_SCOPE: ${{ steps.filter.outputs.ci }}
DESKTOP_SCOPE: ${{ steps.filter.outputs.desktop }}
run: |
set -euo pipefail
case "$CI_SCOPE" in
Expand All @@ -353,6 +369,15 @@ jobs:
exit 1
;;
esac
case "$DESKTOP_SCOPE" in
true|false)
printf 'desktop=%s\n' "$DESKTOP_SCOPE" >> "$GITHUB_OUTPUT"
;;
*)
printf '::error::changes.outputs.desktop was %q, expected true or false\n' "$DESKTOP_SCOPE"
exit 1
;;
esac

- name: Assert the native and matrix outputs are usable
id: matrices
Expand Down Expand Up @@ -1345,11 +1370,11 @@ jobs:
desktop-shell:
name: desktop shell
needs: [changes, gates]
# Native-gated like platform-macos: the Rust shell is formatted, linted
# and tested only when native-capable paths changed.
if: github.event_name != 'pull_request' || (needs.changes.outputs.ci == 'true' && needs.changes.outputs.native == 'true')
# Native shell changes run the Rust checks; package-affecting changes also run the real Linux
# bundle acceptance. The aggregate gate below mirrors this union exactly.
if: github.event_name != 'pull_request' || (needs.changes.outputs.ci == 'true' && (needs.changes.outputs.native == 'true' || needs.changes.outputs.desktop == 'true'))
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
Expand All @@ -1359,7 +1384,11 @@ jobs:
- name: Install Tauri Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf dbus-x11 xvfb xauth wmctrl xdotool openbox

- name: Setup Bun for packaged E2E
if: needs.changes.outputs.desktop == 'true'
uses: ./.github/actions/setup-project-bun

- name: Setup Rust
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master
Expand All @@ -1385,6 +1414,79 @@ jobs:
- name: Run Rust tests
run: cargo test --manifest-path desktop/src-tauri/Cargo.toml

- name: Install packaged E2E dependencies
if: needs.changes.outputs.desktop == 'true'
run: |
bun install --frozen-lockfile
cd desktop
bun install --frozen-lockfile

- name: Build dashboard and bundled sidecar
if: needs.changes.outputs.desktop == 'true'
run: |
bun run build:gui
bun desktop/scripts/prepare-sidecar.ts --target x86_64-unknown-linux-gnu

# Build separately. One format failing must not delete or hide the other
# format's evidence, and neither verification artifact needs an updater key.
- name: Preserve the compiled Linux sidecar
if: needs.changes.outputs.desktop == 'true'
run: chmod +x desktop/scripts/appimage-patchelf.py

- name: Build Linux AppImage
if: needs.changes.outputs.desktop == 'true'
working-directory: desktop
env:
CARGO_TARGET_DIR: ${{ runner.temp }}/opencodex-appimage-target
PATCHELF: ${{ github.workspace }}/desktop/scripts/appimage-patchelf.py
run: bunx tauri build --ci --bundles appimage --config '{"bundle":{"createUpdaterArtifacts":false}}'

- name: Build Linux deb
if: needs.changes.outputs.desktop == 'true'
working-directory: desktop
env:
CARGO_TARGET_DIR: ${{ runner.temp }}/opencodex-deb-target
run: bunx tauri build --ci --bundles deb --config '{"bundle":{"createUpdaterArtifacts":false}}'

- name: Stage isolated Linux bundles
if: needs.changes.outputs.desktop == 'true'
env:
APPIMAGE_BUNDLE: ${{ runner.temp }}/opencodex-appimage-target/release/bundle/appimage
DEB_BUNDLE: ${{ runner.temp }}/opencodex-deb-target/release/bundle/deb
BUNDLE_ROOT: ${{ runner.temp }}/opencodex-linux-bundles
run: |
set -euo pipefail
mkdir -p "$BUNDLE_ROOT/appimage" "$BUNDLE_ROOT/deb"
cp -a "$APPIMAGE_BUNDLE/." "$BUNDLE_ROOT/appimage/"
cp -a "$DEB_BUNDLE/." "$BUNDLE_ROOT/deb/"
chmod -R a-w "$BUNDLE_ROOT"

- name: Run Linux packaged-shell E2E
if: needs.changes.outputs.desktop == 'true'
env:
REPORT_PATH: ${{ runner.temp }}/opencodex-linux-e2e/report.json
run: |
set -euo pipefail
mkdir -p "$(dirname "$REPORT_PATH")"
dbus-run-session -- xvfb-run -a -s '-screen 0 1440x900x24' bash -lc '
openbox >"$RUNNER_TEMP/opencodex-openbox.log" 2>&1 &
wm_pid=$!
trap '\''kill "$wm_pid" 2>/dev/null || true'\'' EXIT
bun desktop/scripts/linux-packaged-e2e.ts \
--bundle-root "$RUNNER_TEMP/opencodex-linux-bundles" \
--report "$REPORT_PATH" \
--version "$(jq -r .version package.json)"
'

- name: Upload Linux packaged-shell E2E report
if: always() && needs.changes.outputs.desktop == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linux-packaged-shell-e2e
path: ${{ runner.temp }}/opencodex-linux-e2e/report.json
if-no-files-found: warn
retention-days: 7

ci:
name: ci
if: always()
Expand Down Expand Up @@ -1414,6 +1516,7 @@ jobs:
CHANGES_SETUP_ACTION: ${{ needs.changes.outputs.setup_action }}
CHANGES_REMOTE_HELPER: ${{ needs.changes.outputs.remote_helper }}
CHANGES_NATIVE: ${{ needs.changes.outputs.native }}
CHANGES_DESKTOP: ${{ needs.changes.outputs.desktop }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
Expand All @@ -1434,15 +1537,19 @@ jobs:
if [ "$EVENT_NAME" = "pull_request" ] && [ "$CHANGES_CI" != "true" ]; then
scoped=not-requested
fi
# platform-macos, widget and desktop-shell carry a compound
# condition: the ordinary scope gate AND the native path filter.
# platform-macos and widget carry the ordinary scope gate AND the native path filter.
# desktop-shell accepts that native set plus the package-E2E set.
# This mirrors that expression exactly; where it disagrees with the
# jobs' own `if:`, the gate fails by name instead of demanding
# success from a job that was deliberately left unselected.
native=not-requested
if [ "$EVENT_NAME" != "pull_request" ] || { [ "$CHANGES_CI" = "true" ] && [ "$CHANGES_NATIVE" = "true" ]; }; then
native=requested
fi
desktop_shell=not-requested
if [ "$EVENT_NAME" != "pull_request" ] || { [ "$CHANGES_CI" = "true" ] && { [ "$CHANGES_NATIVE" = "true" ] || [ "$CHANGES_DESKTOP" = "true" ]; }; }; then
desktop_shell=requested
fi
packaging=not-requested
if [ "$CHANGES_PACKAGING" = "true" ]; then
packaging=requested
Expand Down Expand Up @@ -1499,8 +1606,9 @@ jobs:
changes|select-windows-runner) echo requested ;;
test|storage-policy|api-usage|gates|keyring-smoke|docker-smoke)
echo "$scoped" ;;
platform-macos|widget|desktop-shell)
platform-macos|widget)
echo "$native" ;;
desktop-shell) echo "$desktop_shell" ;;
npm-global-smoke) echo "$packaging" ;;
docs-site-build) echo "$docs" ;;
structure-gate) echo "$structure" ;;
Expand Down
51 changes: 48 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ jobs:
# and updater signatures require maintainer-owned credentials; builds without
# those secrets remain useful for local validation but are not release assets.
- name: Build desktop bundles
if: runner.os != 'Linux'
working-directory: desktop
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
Expand All @@ -429,20 +430,64 @@ jobs:
# diagnostics on the first attempt; Apple signing commands stay non-verbose.
run: bunx tauri ${{ runner.os == 'Linux' && '--verbose' || '' }} build --ci --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --config "${{ runner.os == 'Windows' && format('{0}/opencodex-msi.json', runner.temp) || '{}' }}"

# Tauri patches a bundle-type marker into the application binary for each Linux format.
# Keep each format in its own Cargo target so the deb cannot inherit the AppImage marker
# and linuxdeploy cannot mutate the binary later consumed by the deb build.
- name: Build Linux AppImage bundle
if: runner.os == 'Linux'
working-directory: desktop
env:
CARGO_TARGET_DIR: ${{ runner.temp }}/opencodex-appimage-target
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: bunx tauri build --ci --target ${{ matrix.target }} --bundles appimage

- name: Build Linux deb bundle
if: runner.os == 'Linux'
working-directory: desktop
env:
CARGO_TARGET_DIR: ${{ runner.temp }}/opencodex-deb-target
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: bunx tauri build --ci --target ${{ matrix.target }} --bundles deb

- name: Stage isolated Linux release bundles
if: runner.os == 'Linux'
shell: bash
env:
DESKTOP_TARGET: ${{ matrix.target }}
APPIMAGE_TARGET: ${{ runner.temp }}/opencodex-appimage-target
DEB_TARGET: ${{ runner.temp }}/opencodex-deb-target
run: |
set -euo pipefail
bundle_root="$RUNNER_TEMP/opencodex-linux-release-bundles"
mkdir -p "$bundle_root/appimage" "$bundle_root/deb"
cp -a "$APPIMAGE_TARGET/$DESKTOP_TARGET/release/bundle/appimage/." "$bundle_root/appimage/"
cp -a "$DEB_TARGET/$DESKTOP_TARGET/release/bundle/deb/." "$bundle_root/deb/"
chmod -R a-w "$bundle_root"
echo "DESKTOP_BUNDLE_ROOT=$bundle_root" >> "$GITHUB_ENV"

# After the isolated AppImage exists, and against that staged copy: the default Cargo target
# holds no Linux bundle any more, so verifying there would fail or check a stale artifact.
- name: Verify the packaged Linux sidecar
if: runner.os == 'Linux'
run: bash desktop/scripts/verify-linux-sidecar.sh
run: bash desktop/scripts/verify-linux-sidecar.sh "$DESKTOP_BUNDLE_ROOT/appimage"

- name: Rename release assets
shell: bash
env:
RELEASE_VERSION: ${{ inputs.version }}
DESKTOP_TARGET: ${{ matrix.target }}
run: |
bun desktop/scripts/collect-release-assets.ts \
args=( \
--version "$RELEASE_VERSION" \
--target "$DESKTOP_TARGET" \
--out dist/release
--out dist/release \
)
if [[ -n "${DESKTOP_BUNDLE_ROOT:-}" ]]; then
args+=(--bundle-root "$DESKTOP_BUNDLE_ROOT")
fi
bun desktop/scripts/collect-release-assets.ts "${args[@]}"

# After the bundle exists, not before: a sweep that runs first passes by finding nothing.
- name: Verify every Mach-O in the bundle carries the release identity
Expand Down
Loading
Loading