Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
f764765
fix: release 2.68.0 blockers and bring the menu bar patches to the Wi…
lidge-jun Sep 27, 2026
99d0a94
chore(release): open dev at 2.69.0 before releasing 2.68.0 (#6053)
github-actions[bot] Sep 27, 2026
5a5edf9
docs(devlog): plan merge train round 3 batch 1
lidge-jun Sep 27, 2026
1c9f1fb
test(claude): bind picker proxies without port probes (#6015)
Ingwannu Sep 27, 2026
5747a5c
fix(link): strip provider credentials at relay (#6034)
Ingwannu Sep 27, 2026
5f4784c
fix(cli): derive catalog price estimates for models without manual ov…
codingbooo Sep 27, 2026
e89de8f
fix(plugins): do not trust ACL display names as root (#6019)
Ingwannu Sep 27, 2026
14913d9
test(plugins): pin the current user in the foreign ACL principal cases
lidge-jun Sep 27, 2026
91267a3
fix(desktop): preserve stopped intent after update failure (#6041)
Ingwannu Sep 27, 2026
ead97d2
fix(claude): validate translated strict output schemas (#6006)
Ingwannu Sep 27, 2026
8f25206
test(responses): pin established websocket fallback (#6011)
Ingwannu Sep 27, 2026
4629902
fix(desktop): keep a retry made between update attempts
lidge-jun Sep 27, 2026
bfbcbd6
fix(claude): refuse strict for a non-positive multipleOf
lidge-jun Sep 27, 2026
2bfc18a
docs(structure): record the executed ws-ambiguous-resend proof and it…
lidge-jun Sep 27, 2026
9177663
docs(devlog): close the 2.68.0 release round (#6060)
lidge-jun Sep 27, 2026
8923ad9
Merge pull request #6059 from lidge-jun/codex/train3-b1
lidge-jun Sep 27, 2026
bec870d
docs(devlog): record train 3 B1 review outcome and local proof
lidge-jun Sep 27, 2026
8c7e05d
test(compaction): drain fixture state and close routing history (#6057)
luvs01 Sep 27, 2026
766e58a
fix(grok): account for metered preflight failures (#6035)
luvs01 Sep 27, 2026
429fd60
fix(codebuddy): harden captured parallel tool blocks (#6022)
mdwsk88 Sep 27, 2026
4026640
fix(web-search): release sidecar probes on rejection and response set…
luvs01 Sep 27, 2026
4497ffe
fix(claude): pin Desktop mode when disabling CLI first-party routing …
luvs01 Sep 27, 2026
a3d40e1
fix(cli): show when Claude Desktop keeps the shared proxy env after f…
lidge-jun Sep 27, 2026
79faa24
fix(service): match canonical Codex home ownership (#6036)
luvs01 Sep 27, 2026
43dbcee
fix(service): keep a vanished recorded home foreign
lidge-jun Sep 27, 2026
642cb61
fix(search): bind Devin OAuth to routed provider (#6038)
luvs01 Sep 27, 2026
2849da2
fix(update): isolate pnpm probes and mutation workspaces (#6048)
luvs01 Sep 27, 2026
440e427
chore(integration): compact the update test layout entries below the …
lidge-jun Sep 27, 2026
c2fa265
docs(devlog): record train 3 B2 plan, reviews and local proof
lidge-jun Sep 27, 2026
06d7914
Merge pull request #6061 from lidge-jun/codex/train3-b2
lidge-jun Sep 27, 2026
c609894
docs(devlog): plan train 3 B3
lidge-jun Sep 27, 2026
e964e04
docs(devlog): fold the B3 audit into the plan
lidge-jun Sep 27, 2026
c79fe40
fix(settings): bound Codex ownership config reads (#6049)
luvs01 Sep 27, 2026
33b1920
fix(link): gate join credential on tunnel survival (#6042)
luvs01 Sep 27, 2026
b697756
fix(update): avoid PATH lookup for systemd-run (#6037)
luvs01 Sep 27, 2026
8e08f26
fix(codex): activate quota from saved deadlines (#6020)
terrytan95 Sep 27, 2026
2a49525
fix(codex): key quota activation retries to the credential generation
lidge-jun Sep 27, 2026
9fdc0c4
fix(combos): hold a spent token-plan window for ten minutes
lidge-jun Sep 27, 2026
cafe620
fix(cli): disambiguate auto account selection (#6050)
luvs01 Sep 27, 2026
090e5e1
docs(devlog): record train 3 B3 build and evidence
lidge-jun Sep 27, 2026
bf6c57c
Merge pull request #6062 from lidge-jun/codex/train3-b3
lidge-jun Sep 27, 2026
ac29ec5
docs(devlog): plan train 3 B4
lidge-jun Sep 27, 2026
f83cd2f
fix(codex): preserve transaction recovery fence on release (#6043)
luvs01 Sep 27, 2026
3fc74c0
docs(devlog): record train 3 B4 evidence
lidge-jun Sep 27, 2026
4b3737f
Merge pull request #6063 from lidge-jun/codex/train3-b4
lidge-jun Sep 27, 2026
3a5c3a3
docs(devlog): plan train 3 B5
lidge-jun Sep 27, 2026
79db349
docs(devlog): fold the B5 audit
lidge-jun Sep 27, 2026
4fda15d
fix(command-code): wait out a burst 429 on the canonical key endpoint
lidge-jun Sep 27, 2026
3876151
fix(adapters): protect tiny standalone GLM summary compaction from re…
codingbooo Sep 27, 2026
fb3faab
fix(adapters): narrow the GLM summary budget mitigation
lidge-jun Sep 27, 2026
25e01a8
feat(prompt): snapshot skills catalog per session to preserve prompt …
codingbooo Sep 27, 2026
01b7e24
fix(prompt): snapshot one catalog, only for admitted requests
lidge-jun Sep 27, 2026
ad3b374
docs(management-api): describe remote dashboard sessions as they ship
lidge-jun Sep 27, 2026
2256d09
docs(management-api): use the site-absolute remote hub link
lidge-jun Sep 27, 2026
bbec118
docs(devlog): record train 3 B5 evidence
lidge-jun Sep 27, 2026
7d84593
Merge pull request #6066 from lidge-jun/codex/train3-b5
lidge-jun Sep 27, 2026
28906a3
docs(devlog): plan train 3 B6
lidge-jun Sep 27, 2026
bee2ea4
fix(responses): route direct mcp tool calls through code-mode exec (#…
mdwsk88 Sep 27, 2026
2a383cb
chore(integration): keep the #5925 layout entry on a shared line
lidge-jun Sep 27, 2026
f2727be
fix(status): trust attested live startup health (#5977)
RHODIZSECURITY Sep 27, 2026
6341da9
fix(status): trust a live startup verdict only with the server proof
lidge-jun Sep 27, 2026
58395b5
docs(devlog): record train 3 B6 evidence
lidge-jun Sep 27, 2026
429f4e0
Merge pull request #6069 from lidge-jun/codex/train3-b6
lidge-jun Sep 27, 2026
b181296
docs(devlog): plan train 3 B7
lidge-jun Sep 27, 2026
960e482
fix(gui): bound Kiro status reconciliation (#6025)
Ingwannu Sep 27, 2026
519b9d7
test(gui): avoid duplicate provider hash events (#6010)
Ingwannu Sep 27, 2026
b51e20c
fix(codex): surface remote provider-history filtering (#6007)
Ingwannu Sep 27, 2026
9e92631
docs(devlog): record train 3 B7 evidence
lidge-jun Sep 27, 2026
29cef45
Merge pull request #6070 from lidge-jun/codex/train3-b7
lidge-jun Sep 27, 2026
907e99a
docs(devlog): plan train 3 B8
lidge-jun Sep 27, 2026
159085e
fix(link): make enrollment cancellation and commit share one terminal…
luvs01 Sep 27, 2026
e1c8943
fix(link): bind relay authentication and streaming to one connection …
luvs01 Sep 27, 2026
a5e2f72
fix(search): retain sidecar probe through error response settlement (…
luvs01 Sep 27, 2026
390b69f
fix(claude): report a Windows system proxy that bypasses Desktop firs…
kaladinhonor Sep 27, 2026
eab0ac1
chore(integration): pair the B8 layout entries on one line
lidge-jun Sep 27, 2026
37d0608
docs(devlog): record train 3 B8 evidence
lidge-jun Sep 27, 2026
d0bec2a
Merge pull request #6071 from lidge-jun/codex/train3-b8
lidge-jun Sep 27, 2026
d95c92f
docs(devlog): plan train 3 B9
lidge-jun Sep 27, 2026
2948775
fix(claude): keep picker CA signing key ephemeral (#6072)
luvs01 Sep 27, 2026
40377e7
fix(claude): drop a legacy picker key on every intercept start
lidge-jun Sep 27, 2026
e0c4a95
docs(devlog): record the train 3 outcome
lidge-jun Sep 27, 2026
7f9f0e5
Merge pull request #6073 from lidge-jun/codex/train3-b9
lidge-jun Sep 27, 2026
8371726
fix(codebuddy): bound buffered tool calls
luvs01 Sep 27, 2026
ad78b14
test(coding-agent): cover turn-level translation_buffer_limit path
devin-ai-integration[bot] Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions bin/ocx.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
resolvePnpmGlobalOwner,
runPnpmGlobalUpdate,
} from "../src/update/pnpm-global-install.mjs";
import { PNPM_READ_CWD, withPnpmCommandCwd, pnpmReadEnvironment } from "../src/update/pnpm-read-policy.mjs";
import { checkRegistryPackageIntegrity } from "../src/update/registry-integrity.mjs";
import { hasPendingTeardownIn } from "../src/config/pending-teardown-names.mjs";
import {
Expand Down Expand Up @@ -208,6 +209,8 @@ function runPackageManagerSelfUpdate(manager) {
encoding: "utf8",
timeout: 20_000,
windowsHide: true,
cwd: PNPM_READ_CWD,
env: pnpmReadEnvironment(unprivilegedOwnershipMutationEnvironment(process.env)),
...invocation.options,
});
},
Expand All @@ -221,6 +224,20 @@ function runPackageManagerSelfUpdate(manager) {
const managerInvocation = args => manager === "pnpm"
? pnpmOwnerInvocation(owner, args)
: npmInvocation(args);
// Read-only pnpm probes run from the installed package directory with project pnpmfiles
// disabled, so an attacker-controlled cwd cannot execute hooks during the update check.
const readProbeOptions = invocation => ({
encoding: "utf8",
timeout: 12000,
windowsHide: true,
...(manager === "pnpm"
? {
cwd: PNPM_READ_CWD,
env: pnpmReadEnvironment(unprivilegedOwnershipMutationEnvironment(invocation.env ?? process.env)),
}
: invocation.env ? { env: invocation.env } : {}),
...invocation.options,
});
const latestInvocation = managerInvocation(["view", `${PKG}@${tag}`, "version"]);
const installArgs = manager === "pnpm"
? ["add", "-g", "--allow-build=bun", `${PKG}@${tag}`]
Expand All @@ -230,13 +247,7 @@ function runPackageManagerSelfUpdate(manager) {
console.error(`opencodex: could not resolve ${manager} from a trusted absolute PATH entry; aborting before stopping the proxy.`);
process.exit(1);
}
const latestResult = spawnSync(latestInvocation.file, latestInvocation.args, {
encoding: "utf8",
timeout: 12000,
windowsHide: true,
...(latestInvocation.env ? { env: latestInvocation.env } : {}),
...latestInvocation.options,
});
const latestResult = spawnSync(latestInvocation.file, latestInvocation.args, readProbeOptions(latestInvocation));
const latest = latestResult.status === 0 && typeof latestResult.stdout === "string" ? latestResult.stdout.trim() : "";

console.log(`opencodex v${current} (installed via ${manager}, tag ${tag})`);
Expand All @@ -248,13 +259,7 @@ function runPackageManagerSelfUpdate(manager) {
const integrity = checkRegistryPackageIntegrity(PKG, latest || null, args => {
const invocation = managerInvocation(args);
if (!invocation) return { status: 1 };
return spawnSync(invocation.file, invocation.args, {
encoding: "utf8",
timeout: 12000,
windowsHide: true,
...(invocation.env ? { env: invocation.env } : {}),
...invocation.options,
});
return spawnSync(invocation.file, invocation.args, readProbeOptions(invocation));
});
if (integrity.ok === false) {
console.error(`opencodex: ${integrity.reason}; aborting before stopping the proxy.`);
Expand Down Expand Up @@ -768,14 +773,17 @@ function runPackageManagerSelfUpdate(manager) {
runPnpm: (args, capture = false) => {
const invocation = pnpmOwnerInvocation(owner, args);
if (!invocation) return { status: 1 };
return spawnSync(invocation.file, invocation.args, {
return withPnpmCommandCwd(args, cwd => spawnSync(invocation.file, invocation.args, {
...invocation.options,
stdio: capture ? "pipe" : "inherit",
encoding: "utf8",
timeout: 180000,
windowsHide: true,
env: unprivilegedOwnershipMutationEnvironment(invocation.env ?? process.env),
});
// Reads probe from the package dir; mutations (add -g, rollback) must not
// keep a cwd handle inside the package Windows is replacing.
cwd,
env: pnpmReadEnvironment(unprivilegedOwnershipMutationEnvironment(invocation.env ?? process.env)),
}));
},
log: line => console.log(line),
});
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "opencodex-desktop"
version = "2.68.0"
version = "2.69.0"
description = "OpenCodex desktop shell"
authors = ["OpenCodex contributors"]
license = "MIT"
Expand Down
142 changes: 132 additions & 10 deletions desktop/src-tauri/src/exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ pub struct Supervision {
pub reason_set: bool,
}

/// State restored when an update's coordinated restart is abandoned.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AbortedRestart {
pub phase: ExitPhase,
/// Whether the person wanted a runtime before the drain or requested one while it ran.
pub runtime_was_wanted: bool,
}

impl Supervision {
/// Nothing is in flight, nobody asked for the runtime to stop, and the app is not ending.
pub fn allowed(self) -> bool {
Expand All @@ -161,6 +169,14 @@ struct Inner {
deferred: bool,
/// See [`Supervision::wanted`]. Sticky: finishing a stop does not restore it.
wanted: bool,
/// The intent an update temporarily replaced with `wanted=false`; consumed if it aborts.
restart_wanted: Option<bool>,
}

fn remember_restart_intent(inner: &mut Inner, reason: ExitReason, prior_wanted: bool) {
if reason == ExitReason::CoordinatedRestart {
inner.restart_wanted.get_or_insert(prior_wanted);
}
}

/// The exit sequence's state, managed by the app.
Expand All @@ -179,6 +195,7 @@ impl ExitCoordinator {
hides_to_tray: TrayAvailability::assumed().hides_to_tray(),
deferred: false,
wanted: true,
restart_wanted: None,
}),
}
}
Expand Down Expand Up @@ -215,17 +232,20 @@ impl ExitCoordinator {
/// [`ExitCoordinator::finish_stop`] is holding the phase.
pub fn claim_drain(&self, fallback: ExitReason) -> Option<ExitReason> {
let mut inner = self.inner();
let prior_wanted = inner.wanted;
// A quit or an update is on its way, whoever ends up running the drain: the runtime it
// stops is not one to bring back.
inner.wanted = false;
match inner.phase {
ExitPhase::Idle => {
let reason = *inner.reason.get_or_insert(fallback);
remember_restart_intent(&mut inner, reason, prior_wanted);
inner.phase = ExitPhase::Draining;
Some(reason)
}
ExitPhase::Spawning | ExitPhase::Stopping => {
inner.reason.get_or_insert(fallback);
let reason = *inner.reason.get_or_insert(fallback);
remember_restart_intent(&mut inner, reason, prior_wanted);
inner.deferred = true;
None
}
Expand All @@ -235,6 +255,7 @@ impl ExitCoordinator {
// is the one thing a user with a runtime that would not stop cannot easily do.
ExitPhase::DrainFailed | ExitPhase::OwnershipUnknown => {
let reason = *inner.reason.get_or_insert(fallback);
remember_restart_intent(&mut inner, reason, prior_wanted);
inner.phase = ExitPhase::Draining;
Some(reason)
}
Expand All @@ -256,10 +277,12 @@ impl ExitCoordinator {
/// An update drains before it installs. When the install then fails, or the drain itself did,
/// the drain's phase used to be the end of the road: `Drained` is terminal, so no runtime could
/// be started again and a bare window close quit the app. This returns the app to `Idle` with
/// no claimed reason and wants a runtime again, which is what a successful update would have
/// ended in too. It touches nothing unless an update's restart holds the phase: a quit is never
/// aborted, and a drain still running belongs to whoever runs it. Returns the phase it left.
pub fn abort_restart(&self) -> Option<ExitPhase> {
/// no claimed reason and restores the runtime intent the update temporarily suppressed. A
/// retry requested while the drain was in flight wins too: aborting an older update must not
/// overwrite newer user intent. It touches nothing unless an update's restart holds the phase:
/// a quit is never aborted, and a
/// drain still running belongs to whoever runs it. Returns the phase and restored intent.
pub fn abort_restart(&self) -> Option<AbortedRestart> {
let mut inner = self.inner();
let left = inner.phase;
let restart = inner.reason == Some(ExitReason::CoordinatedRestart);
Expand All @@ -273,8 +296,15 @@ impl ExitCoordinator {
inner.phase = ExitPhase::Idle;
inner.reason = None;
inner.deferred = false;
inner.wanted = true;
Some(left)
// `resume` can arrive after the update captured its original intent. Preserve that newer
// request as well as the older snapshot; otherwise the abort races the startup retry and
// can leave a runtime stopped even though the person just asked for it.
let runtime_was_wanted = inner.wanted || inner.restart_wanted.take().unwrap_or(false);
inner.wanted = runtime_was_wanted;
Some(AbortedRestart {
phase: left,
runtime_was_wanted,
})
}

/// What the runtime supervisor reads before it acts.
Expand All @@ -293,7 +323,14 @@ impl ExitCoordinator {

/// A person asked for a runtime again (the startup page's retry).
pub fn resume(&self) {
self.inner().wanted = true;
let mut inner = self.inner();
inner.wanted = true;
// A pending update snapshot must learn about the request too. A retried install that finds
// the drain already settled clears `wanted` again without replacing the snapshot, so a
// retry recorded only in `wanted` would be lost when that install fails and aborts.
if let Some(snapshot) = inner.restart_wanted.as_mut() {
*snapshot = true;
}
}

/// Reserve the right to start a runtime. False once something else owns the phase.
Expand Down Expand Up @@ -624,7 +661,7 @@ fn hide_windows(app: &AppHandle) {
#[cfg(test)]
mod tests {
use super::{
decide, DrainVerdict, ExitCoordinator, ExitDecision, ExitPhase, ExitReason,
decide, AbortedRestart, DrainVerdict, ExitCoordinator, ExitDecision, ExitPhase, ExitReason,
RestartReadiness, Supervision,
};
use crate::tray_availability::TrayAvailability;
Expand Down Expand Up @@ -707,7 +744,13 @@ mod tests {
);
coordinator.finish_drain(verdict);
let left = coordinator.phase();
assert_eq!(coordinator.abort_restart(), Some(left));
assert_eq!(
coordinator.abort_restart(),
Some(AbortedRestart {
phase: left,
runtime_was_wanted: true,
})
);
assert_eq!(coordinator.phase(), ExitPhase::Idle);
// A bare close hides again instead of quitting out of a terminal phase.
assert_eq!(coordinator.decision(), ExitDecision::Hide);
Expand All @@ -716,6 +759,85 @@ mod tests {
}
}

#[test]
fn a_failed_update_preserves_a_completed_tray_stop() {
let coordinator = ExitCoordinator::new();
coordinator.set_tray(TrayAvailability::Available);
assert!(coordinator.begin_stop());
assert_eq!(coordinator.finish_stop(), None);
assert!(!coordinator.supervision().wanted);

assert_eq!(
coordinator.claim_drain(ExitReason::CoordinatedRestart),
Some(ExitReason::CoordinatedRestart)
);
coordinator.finish_drain(DrainVerdict::Drained);
assert_eq!(
coordinator.abort_restart(),
Some(AbortedRestart {
phase: ExitPhase::Drained,
runtime_was_wanted: false,
})
);
assert_eq!(coordinator.phase(), ExitPhase::Idle);
assert_eq!(coordinator.decision(), ExitDecision::Hide);
assert!(!coordinator.supervision_allowed());
}

#[test]
fn a_startup_retry_during_an_update_drain_is_not_overwritten_by_abort() {
let coordinator = ExitCoordinator::new();
coordinator.set_tray(TrayAvailability::Available);
assert!(coordinator.begin_stop());
assert_eq!(coordinator.finish_stop(), None);
assert!(!coordinator.supervision().wanted);

assert_eq!(
coordinator.claim_drain(ExitReason::CoordinatedRestart),
Some(ExitReason::CoordinatedRestart)
);
coordinator.resume();
// The ending claim still prevents supervision until the failed update is handed back.
assert!(!coordinator.supervision_allowed());
coordinator.finish_drain(DrainVerdict::Drained);
assert_eq!(
coordinator.abort_restart(),
Some(AbortedRestart {
phase: ExitPhase::Drained,
runtime_was_wanted: true,
})
);
assert!(coordinator.supervision_allowed());
}

#[test]
fn a_retry_between_update_attempts_survives_the_second_claim() {
let coordinator = ExitCoordinator::new();
coordinator.set_tray(TrayAvailability::Available);
assert!(coordinator.begin_stop());
assert_eq!(coordinator.finish_stop(), None);

assert_eq!(
coordinator.claim_drain(ExitReason::CoordinatedRestart),
Some(ExitReason::CoordinatedRestart)
);
coordinator.finish_drain(DrainVerdict::Drained);
coordinator.resume();
// The next install attempt finds the drain settled and clears `wanted` again.
assert_eq!(
coordinator.claim_drain(ExitReason::CoordinatedRestart),
None
);
assert_eq!(
coordinator.abort_restart(),
Some(AbortedRestart {
phase: ExitPhase::Drained,
runtime_was_wanted: true,
})
);
assert!(coordinator.supervision_allowed());
}

#[test]
fn a_quit_or_a_drain_in_flight_is_never_aborted() {
let coordinator = ExitCoordinator::new();
Expand Down
17 changes: 16 additions & 1 deletion desktop/src-tauri/src/native_tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,21 @@ fn publish(app: &AppHandle, generation: u64, binding: Option<RuntimeBinding>, sn
*state
.cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = (binding, snapshot);
.unwrap_or_else(std::sync::PoisonError::into_inner) = (binding, cached(snapshot));
}
});
}

/// The cached copy every later refresh starts from. `switchFailed` answers one switch, so it is
/// delivered once and never cached: carried forward, it would settle the next switch's spinner on
/// that switch's first loading publish.
fn cached(mut snapshot: Value) -> Value {
if let Some(fields) = snapshot.as_object_mut() {
fields.remove("switchFailed");
}
snapshot
}

fn display_payload(snapshot: Value) -> Option<(Value, Vec<u8>)> {
let bytes = serde_json::to_vec(&snapshot).ok()?;
if bytes.len() <= 8 * 1024 * 1024 {
Expand Down Expand Up @@ -406,6 +416,11 @@ mod tests {
}
#[test]
fn refresh_failure_preserves_age_and_clears_busy_state() {
let reported = json!({"refreshing":false,"errors":["refused"],"switchFailed":true});
let kept = cached(reported);
assert!(kept.get("switchFailed").is_none());
assert_eq!(kept["errors"], json!(["refused"]));

let before = json!({"updatedAt":12,"refreshing":true,"today":{"totalTokens":30}});
let after = failed(before, "Unavailable");
assert_eq!(after["updatedAt"], 12);
Expand Down
Loading
Loading