Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d677804
fix(oauth): allow dismissing a settled failed login flow (carry #7379)
lidge-jun Oct 19, 2026
c9e382b
fix(providers): preserve explicit API-key mode when keys are unavaila…
lidge-jun Oct 19, 2026
5044df7
fix(dsh): bound account profile response reads (carry #7333)
lidge-jun Oct 19, 2026
488b430
test(dsh): drain server fixtures before removing homes (carry #7305)
lidge-jun Oct 19, 2026
4237fd5
fix(claude-cli): keep local errors out of subscription cooldown (carr…
lidge-jun Oct 19, 2026
83704ae
fix(claude): bound Desktop library reads and preflight writes (carry …
lidge-jun Oct 19, 2026
b3a824e
fix(service): stop scheduler wrappers before Windows reinstall (carry…
lidge-jun Oct 19, 2026
c0f0510
fix(startup): require RunAtLoad before Desktop relaunch (carry #7277)
lidge-jun Oct 19, 2026
6c773f8
fix(codex): strip OpenSSL startup variables from updater child env (c…
lidge-jun Oct 19, 2026
c00446c
fix(codex): keep the service token out of bypassed shim launches
lidge-jun Oct 19, 2026
fdf2caf
test(claude): wait for a real ctime transition before requiring rejec…
lidge-jun Oct 19, 2026
bb04776
fix(google-antigravity): admit a probed project_id fallback in accoun…
lidge-jun Oct 19, 2026
03ef0d9
fix(quota): name a closed reason when an OAuth account quota probe fails
lidge-jun Oct 19, 2026
a3026aa
test(claude): match the symlink-resolved rollback target in the picke…
lidge-jun Oct 19, 2026
48aad0a
fix(google-antigravity): cancel the import project probe with the imp…
lidge-jun Oct 19, 2026
7de0445
fix(codex): upgrade installed Windows shims that load the token befor…
lidge-jun Oct 19, 2026
7eec3cd
test(codex): mark the in-place Windows diagnostics fixture with the c…
lidge-jun Oct 19, 2026
b070ff0
fix(codex): refresh obsolete Windows shims without clobbering a concu…
lidge-jun Oct 19, 2026
97d5575
docs(structure): record the Windows shim refresh transaction
lidge-jun Oct 19, 2026
c0ba8c3
fix(codex): create the Windows shim refresh stage exclusively and res…
lidge-jun Oct 19, 2026
9492d61
fix(codex): bind Windows shim refresh rollback and recovery to observ…
lidge-jun Oct 19, 2026
b1ffd89
fix(codex): never displace a Windows shim where its replacement canno…
lidge-jun Oct 19, 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
6 changes: 6 additions & 0 deletions docs-site/src/content/docs/guides/claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,12 @@ Claude Desktop can use OpenCodex in one of two mutually exclusive modes. Pick it
**Connect → Claude Desktop → Connection mode** in the dashboard or with `ocx claude desktop apply
--first-party|--gateway`.

> Desktop library profiles and `_meta.json` must be regular files of at most
> 4 MiB. OpenCodex also refuses generated profile or metadata output above that
> limit before replacing files. Back up and reduce oversized configuration
> manually; a size refusal does not truncate the existing data. Connection-owned
> stores retain their existing tighter limits.

### Gateway (default)

New installs use **gateway** by default. Its third-party profile, described below, switches the
Expand Down
2 changes: 2 additions & 0 deletions docs-site/src/content/docs/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,8 @@ cat accounts.json | ocx account import google-antigravity --format cockpit-tools

Inline JSON and extra positional arguments are rejected. Keep exported files private and delete or store them securely after import.

Each record may carry an optional `project_id` for an account whose Cloud Code Assist project is not discovered automatically. It must be a Google Cloud project id (6–30 lowercase letters, digits or hyphens). OpenCodex uses it only when discovery finds no project, and only after a quota check with that account's own token succeeds for it; otherwise the record fails with `missing_project` and nothing is saved. A discovered project always takes precedence.

### OAuth reliability

opencodex coordinates token refresh and Codex pool routing so concurrent requests do not race the
Expand Down
11 changes: 10 additions & 1 deletion docs-site/src/content/docs/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ Labels are read on login/import or refresh, never inferred from quota percentage
| `GET /api/key-providers` | List providers configured through API-key login | — |
| `POST /api/oauth/login` | Start an OAuth login or account-add flow. `mode: "code"` starts a code-display login (Anthropic only) and returns `mode`, `flowId`, `expiresAt` | 400 unknown/invalid provider, invalid `mode`, `code_mode_unsupported`; `oauth_mutation_busy` |
| `POST /api/oauth/login/code` | Submit a manual callback URL or authorization code. For a code-display flow (with `flowId`) returns the token-exchange outcome `{ok, account}` / `{ok:false, error}` | 400 invalid provider/code, `state_mismatch`, `invalid_or_expired_code`, `provider_unreachable`, `malformed_input`; 409 `no_pending_login`; `oauth_mutation_busy` |
| `POST /api/oauth/login/cancel` | Cancel a public in-progress OAuth flow; an optional `flowId` names one code-display flow (a flow of another provider is refused) | 400 unknown provider |
| `POST /api/oauth/login/cancel` | Cancel a public in-progress OAuth flow or dismiss its settled failure; an optional `flowId` names one code-display flow (a flow of another provider is refused) | 400 unknown provider |
| `GET /api/oauth/status` | Poll one provider's OAuth flow | 400 unknown provider |
| `POST /api/oauth/logout` | Remove the selected provider credential | 400 unknown provider; `oauth_mutation_busy` |
| `GET /api/oauth/accounts` | List masked accounts; Anthropic and generic OAuth account rows include their `paused` state. Kiro rows include `autoSelectable` and a closed `skipReason` when excluded from automatic selection; an active singleton may still send. Quota remains opt-in. | 400 invalid provider |
Expand All @@ -625,6 +625,9 @@ Labels are read on login/import or refresh, never inferred from quota percentage
| `PUT /api/providers/keys/alias` | Set or clear a provider-key alias | 400 invalid input; 404 provider/key missing |
| `GET, POST, PATCH, DELETE /api/keys` | List, create, edit, or delete data-plane admission keys | 400 invalid body/id; 404 key missing |

`POST /api/oauth/login/cancel` can also dismiss the selected settled failed login attempt. It leaves stored credentials and successful login results intact; status then reports the remaining flow or idle. Cancelling an in-progress attempt still records its cancelled outcome.


For Anthropic, `routes` is an ordered array of `{name, match, accounts, fallback?}` rules on both settings endpoints. GET and write echoes include `routes` (`null` when absent); unified DTOs expose `routes: null` for other kinds. A supplied `routes` on another kind is rejected. Omission preserves rules, `[]` matches nothing, and `null` clears them. The `accounts` values are stored IDs; removed IDs remain valid in a rule so re-adding an account can restore routing. Management responses retain valid configured names; request logs identify a match only as `route:#<n>` (1-based list position). If a hand-edited stored rule is invalid, both settings GETs return `routes: null` and a `routesError` diagnostic instead of presenting that rule as valid; the stored value remains available for correction. Valid or absent rules omit `routesError`.

Credential list responses are deliberately masked. OAuth access tokens and complete provider API
Expand Down Expand Up @@ -679,6 +682,12 @@ When Antigravity's quota-summary endpoint has no usable response, quota reports
per-model Cloud Code Assist usage windows when the model-discovery response provides them. These
measurements are diagnostic; an absent model window does not establish that the model is unavailable.

With `quota=1`, an Anthropic or Antigravity account row whose probe returned no quota carries
`quotaUnavailable: true` and, when the cause is known, a closed `quotaFailure` code:
`account_unavailable` (sign the account in again), `access_denied`, `rate_limited`, `timeout`,
`transport_error`, `upstream_error` or `response_unusable` (retry later), among others. The code
never includes upstream text, and a quota failure never changes the row's `health`.

The provider context-cap response includes `caps` (active limits) and `values` (last selected
values, retained while disabled). Enabling a provider without `value` restores its selection,
or uses the global `contextCapValue` on first enable. This also applies to OpenAI: the switch
Expand Down
7 changes: 6 additions & 1 deletion scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": 1,
"root": "tests",
"explicitShards": ["layout-clients.json"],
"explicit": { "combo-description.test.ts": "routing", "combo-description-catalog.test.ts": "codex-integration", "codex-provider-routing.test.ts": "codex-integration", "config-native-reasoning-retention.test.ts": "config", "server-native-reasoning-retention-merge.test.ts": "server", "server-native-reasoning-retention.test.ts": "server", "responses-retained-reasoning-stream-recovery.test.ts": "responses", "openai-native-hosted-search-history.test.ts": "adapters/openai", "responses-native-compact-search-history.test.ts": "responses", "config-hosted-search-projection.test.ts": "config", "gcp-credential-boundary.test.ts": "adapters/google", "gcp-sa-marker.test.ts": "adapters/google", "gcp-sa-adapter.test.ts": "adapters/google", "management-gcp-credential-input.test.ts": "server", "codex-preserve-signed-in.test.ts": "codex-integration", "cli-account-preservation.test.ts": "cli", "service-state-isolation.test.ts": "service", "compaction-history-framing.test.ts": "responses", "catalog-native-max-context.test.ts": "codex-integration", "protocol-argon-thought-boundary.test.ts": "responses", "service-log.test.ts": "service", "messages-native-empty-inline.test.ts": "claude-integration", "catalog-auto-review-override.test.ts": "codex-integration", "management-auto-review-settings.test.ts": "server", "management-auto-review-auth.test.ts": "server", "prefer-model-alias-ids.test.ts": "server", "responses-mimo-envelope.test.ts": "responses", "responses-call-id-linkage.test.ts": "responses", "bridge-terminal-refusal.test.ts": "adapters", "loop-terminal-refusal.test.ts": "images", "web-search-terminal-refusal.test.ts": "web-search", "combo-terminal-refusal.test.ts": "routing", "server-terminal-refusal-wiring.test.ts": "server", "incomplete-reason-normalization.test.ts": "responses", "advisor-retention.test.ts": "advisor", "responses-native-key-cooldown.test.ts": "responses", "responses-run-turn-key-readmission.test.ts": "responses", "qoder-spawn-readmission.test.ts": "adapters", "api-key-selection-cooldown.test.ts": "providers", "api-key-auto-pause-distribution.test.ts": "providers", "server-chat-native-key-selection.test.ts": "server", "compact-key-selection.test.ts": "responses", "messages-native-key-selection.test.ts": "claude-integration",
"explicit": { "combo-description.test.ts": "routing", "combo-description-catalog.test.ts": "codex-integration", "codex-provider-routing.test.ts": "codex-integration", "config-native-reasoning-retention.test.ts": "config", "server-native-reasoning-retention-merge.test.ts": "server", "server-native-reasoning-retention.test.ts": "server", "responses-retained-reasoning-stream-recovery.test.ts": "responses", "openai-native-hosted-search-history.test.ts": "adapters/openai", "responses-native-compact-search-history.test.ts": "responses", "config-hosted-search-projection.test.ts": "config", "gcp-credential-boundary.test.ts": "adapters/google", "gcp-sa-marker.test.ts": "adapters/google", "gcp-sa-adapter.test.ts": "adapters/google", "management-gcp-credential-input.test.ts": "server", "codex-preserve-signed-in.test.ts": "codex-integration", "cli-account-preservation.test.ts": "cli", "service-state-isolation.test.ts": "service", "compaction-history-framing.test.ts": "responses", "catalog-native-max-context.test.ts": "codex-integration", "protocol-argon-thought-boundary.test.ts": "responses", "service-log.test.ts": "service", "messages-native-empty-inline.test.ts": "claude-integration", "catalog-auto-review-override.test.ts": "codex-integration", "management-auto-review-settings.test.ts": "server", "management-auto-review-auth.test.ts": "server", "prefer-model-alias-ids.test.ts": "server", "responses-mimo-envelope.test.ts": "responses", "responses-call-id-linkage.test.ts": "responses", "bridge-terminal-refusal.test.ts": "adapters", "loop-terminal-refusal.test.ts": "images", "web-search-terminal-refusal.test.ts": "web-search", "combo-terminal-refusal.test.ts": "routing", "server-terminal-refusal-wiring.test.ts": "server", "incomplete-reason-normalization.test.ts": "responses", "advisor-retention.test.ts": "advisor", "responses-native-key-cooldown.test.ts": "responses", "responses-run-turn-key-readmission.test.ts": "responses", "qoder-spawn-readmission.test.ts": "adapters", "api-key-selection-cooldown.test.ts": "providers", "api-key-auto-pause-distribution.test.ts": "providers", "explicit-key-pause-oauth-boundary.test.ts": "providers", "server-chat-native-key-selection.test.ts": "server", "compact-key-selection.test.ts": "responses", "messages-native-key-selection.test.ts": "claude-integration",
"openai-chat-standalone-bare-block.test.ts": "adapters/openai",
"owner-registry-isolation.test.ts": "config",
"responses-auto-pause-replay-owner.test.ts": "responses", "responses-account-auto-pause.test.ts": "responses", "oauth-account-auto-pause.test.ts": "oauth", "responses-account-auto-pause-wire-owner.test.ts": "responses", "oauth-upsert-paused-api-key.test.ts": "oauth", "management-account-auto-pause.test.ts": "server", "cli-account-key-pause.test.ts": "cli", "oauth-account-auto-pause-revision.test.ts": "oauth", "config-account-auto-pause-revisions.test.ts": "config", "codex-history-pending-wal.test.ts": "codex-integration", "responses-anthropic-billing-send-budget.test.ts": "responses", "anthropic-billing-ownership.test.ts": "adapters/anthropic", "server-anthropic-billing.test.ts": "server", "server-anthropic-billing-boundaries.test.ts": "server", "router-anthropic-billing-failover.test.ts": "routing", "anthropic-billing-refusal.test.ts": "adapters/anthropic", "messages-request-id-headers.test.ts": "claude-integration", "bounded-file-read.test.ts": "lib", "responses-undeclared-tool-guard-inbound.test.ts": "responses", "explicit-empty-tool-catalog.test.ts": "responses", "explicit-empty-tool-catalog-history.test.ts": "responses",
Expand Down Expand Up @@ -142,6 +142,7 @@
"tool-envelope-echo-whole-line.test.ts": "adapters",
"abort-race.test.ts": "adapters",
"account-import.test.ts": "server",
"account-import-project-fallback.test.ts": "server",
"account-pool-management-api.test.ts": "server",
"link-listener-admission.test.ts": "server", "link-management-relay.test.ts": "server",
"link-listener-lifecycle.test.ts": "server", "link-listener-mutation.test.ts": "server",
Expand Down Expand Up @@ -711,6 +712,8 @@
"codex-systemd-env-probe.test.ts": "codex-integration",
"codex-shim-autorestore.test.ts": "codex-integration",
"codex-shim-destroyed-probe.test.ts": "codex-integration",
"codex-shim-bypass-token.test.ts": "codex-integration",
"codex-shim-windows-revision.test.ts": "codex-integration",
"codex-shim-ensure-failure.test.ts": "codex-integration",
"codex-shim-readiness.test.ts": "codex-integration",
"codex-shim-standalone.test.ts": "codex-integration",
Expand Down Expand Up @@ -1410,6 +1413,7 @@
"provider-account-quota-persistence.test.ts": "providers",
"provider-account-quota-routes.test.ts": "server",
"provider-account-quota.test.ts": "providers",
"provider-account-quota-failure-reasons.test.ts": "providers",
"provider-api-keys.test.ts": "providers",
"provider-capacity.test.ts": "providers",
"provider-config-batch-management.test.ts": "providers",
Expand Down Expand Up @@ -1690,6 +1694,7 @@
"service-auth-qualified-localhost.test.ts": "service",
"service-child-ownership.test.ts": "service",
"service-handled-signal-exit.test.ts": "service", "service-health-sentinel-linux.test.ts": "service", "service-health-sentinel-launch.test.ts": "service", "service-health-sentinel-policy.test.ts": "service", "service-health-sentinel-process.test.ts": "service", "service-health-sentinel-runner.test.ts": "service",
"service-install-wrapper-cleanup.test.ts": "service",
"service-claim.test.ts": "service",
"service-ownership-compatibility.test.ts": "service",
"service-ownership-handover.test.ts": "service",
Expand Down
10 changes: 4 additions & 6 deletions src/adapters/claude-cli/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,10 @@ export function createClaudeCliAdapter(provider: OcxProviderConfig, deps: Claude
provider,
parsed,
incoming,
emit: withClaudeLoginHint(event => {
if (event.type === "error") {
void Promise.resolve((deps.usageRefusal ?? recordClaudeUsageRefusal)(event.message)).catch(() => {});
}
emit(event);
}),
emit: withClaudeLoginHint(emit),
onCliOutputError: message => {
void Promise.resolve().then(() => (deps.usageRefusal ?? recordClaudeUsageRefusal)(message)).catch(() => {});
},
...(toolBridge ? { toolBridge } : {}),
conversationInput: buildStableClaudeConversationInput,
buildArgs: (profile, req, prov) => buildArgs(profile as ClaudeCliProfile, req, prov, promptFile),
Expand Down
18 changes: 14 additions & 4 deletions src/adapters/coding-agent/turn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ export interface CodingAgentTurnInput {
parsed: OcxParsedRequest;
incoming: IncomingMeta;
emit: (event: AdapterEvent) => void;
/** Observes accepted CLI result/stderr failures, before client-side error decoration. */
onCliOutputError?: (message: string) => void;
/** Family-specific headless argument builder (tools disabled, model, reasoning, system prompt). */
buildArgs: (profile: CodingAgentProviderProfile, parsed: OcxParsedRequest, provider: OcxProviderConfig) => string[];
/** Family-specific scoped env builder (credential + region switch on top of baseScopedEnv). */
Expand Down Expand Up @@ -368,11 +370,18 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
});

let terminalEmitted = false;
const emitOnce = (event: AdapterEvent): void => {
const emitOnce = (event: AdapterEvent, cliOutputError?: string): void => {
if (event.type === "done" || event.type === "error" || event.type === "incomplete") {
if (terminalEmitted) return;
terminalEmitted = true;
}
if (event.type === "error" && cliOutputError !== undefined) {
try {
input.onCliOutputError?.(cliOutputError);
} catch {
// An observer failure must never drop the client's terminal event or turn it into a rejection.
}
}
emit(event);
};

Expand Down Expand Up @@ -648,7 +657,8 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
}
emitOnce(event.type === "error"
? { ...event, message: redactSecrets(event.message, profile.tokenEnv, apiKey) }
: event);
: event, event.type === "error" && message.type === "result"
? redactSecrets(event.message, profile.tokenEnv, apiKey) : undefined);
}
if (failClosed) break;
const sawToolTurnStop = sawSelectedToolTurnStop();
Expand Down Expand Up @@ -762,7 +772,7 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
};
emitOnce(input.classifyFailure
? input.classifyFailure({ source: "process-exit", exitCode: child.exitCode, stderr, error, profile })
: error);
: error, stderr || undefined);
} else if (toolBridge && deferredResultDone !== undefined && !sawSelectedToolTurnStop()) {
emitOnce({
type: "error",
Expand All @@ -783,7 +793,7 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
errorType: "upstream_error",
code: "protocol_error",
retryable: false,
});
}, stderr || undefined);
}
}
} finally {
Expand Down
Loading
Loading