Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions docs/privacy.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- No custom analytics pipeline in this repository.
- No project-owned remote database.
- Network calls are limited to required OAuth/backend/update endpoints.
- Local structured telemetry is written to `~/.codex/multi-auth/logs/product-telemetry.jsonl`.
- Set `CODEX_AUTH_TELEMETRY_ENABLED=0` (or `telemetryEnabled: false`) to disable local telemetry writes.

---

Expand All @@ -21,6 +23,7 @@
| Flagged accounts | `~/.codex/multi-auth/openai-codex-flagged-accounts.json` | Accounts with hard auth failures |
| Quota cache | `~/.codex/multi-auth/quota-cache.json` | Cached quota snapshots |
| Logs | `~/.codex/multi-auth/logs/codex-plugin/` | Optional diagnostics |
| Local telemetry ledger | `~/.codex/multi-auth/logs/product-telemetry.jsonl` | Local event timeline for CLI/plugin outcomes |
| Prompt/cache files | `~/.codex/multi-auth/cache/` | Cached prompt/template metadata |
| Codex CLI state | `~/.codex/accounts.json`, `~/.codex/auth.json` | Official Codex CLI files |

Expand Down Expand Up @@ -60,6 +63,7 @@ rm -f ~/.codex/multi-auth/openai-codex-accounts.json
rm -f ~/.codex/multi-auth/openai-codex-flagged-accounts.json
rm -f ~/.codex/multi-auth/quota-cache.json
rm -rf ~/.codex/multi-auth/logs/codex-plugin
rm -f ~/.codex/multi-auth/logs/product-telemetry.jsonl*
rm -rf ~/.codex/multi-auth/cache
# Override-root cleanup examples (if overrides are set):
[ -n "${CODEX_MULTI_AUTH_DIR:-}" ] && [ -d "$CODEX_MULTI_AUTH_DIR/logs/codex-plugin" ] && rm -rf "$CODEX_MULTI_AUTH_DIR/logs/codex-plugin"
Expand All @@ -74,6 +78,7 @@ Remove-Item "$HOME\.codex\multi-auth\openai-codex-accounts.json" -Force -ErrorAc
Remove-Item "$HOME\.codex\multi-auth\openai-codex-flagged-accounts.json" -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\quota-cache.json" -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\logs\codex-plugin" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\logs\product-telemetry.jsonl*" -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\cache" -Recurse -Force -ErrorAction SilentlyContinue
# Override-root cleanup examples (if overrides are set):
if ($env:CODEX_MULTI_AUTH_DIR) { Remove-Item "$env:CODEX_MULTI_AUTH_DIR\\*" -Recurse -Force -ErrorAction SilentlyContinue }
Expand Down
9 changes: 8 additions & 1 deletion docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,27 @@ Compatibility aliases are supported:
| `codex auth report` | Generate full health report |
| `codex auth fix` | Apply safe account storage fixes |
| `codex auth doctor` | Run diagnostics and optional repairs |
| `codex auth telemetry` | Show local telemetry summary and recent events |

---

## Common Flags

| Flag | Applies to | Meaning |
| --- | --- | --- |
| `--json` | verify-flagged, forecast, report, fix, doctor | Print machine-readable output |
| `--json` | verify-flagged, forecast, report, fix, doctor, telemetry | Print machine-readable output |
| `--live` | forecast, report, fix | Use live probe before decisions/output |
| `--dry-run` | verify-flagged, fix, doctor | Preview without writing storage |
| `--model <model>` | forecast, report, fix | Specify model for live probe paths |
| `--out <path>` | report | Write report output to file |
| `--fix` | doctor | Apply safe repairs |
| `--no-restore` | verify-flagged | Verify only; do not restore healthy flagged accounts |
| `--since-hours <hours>` | telemetry | Include events newer than this many hours |
| `--limit <n>` | telemetry | Max events to include in report |
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Telemetry rollout pointer:
- Upgrade notes: [../upgrade.md](../upgrade.md)
- Automation rollout: add a script alias such as `auth:telemetry` -> `codex auth telemetry --json --since-hours 24 --limit 200`

---

Expand Down
4 changes: 3 additions & 1 deletion docs/reference/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Examples:
- `sessionAffinityTtlMs`
- `sessionAffinityMaxEntries`
- `perProjectAccounts`
- `telemetryEnabled`

### Rotation and Quota

Expand Down Expand Up @@ -128,6 +129,7 @@ Common operator overrides:
- `CODEX_TUI_GLYPHS`
- `CODEX_AUTH_FETCH_TIMEOUT_MS`
- `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS`
- `CODEX_AUTH_TELEMETRY_ENABLED`

---

Expand Down Expand Up @@ -175,4 +177,4 @@ codex auth forecast --live

- [commands.md](commands.md)
- [storage-paths.md](storage-paths.md)
- [../configuration.md](../configuration.md)
- [../configuration.md](../configuration.md)
78 changes: 76 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
getAutoResume,
getToastDurationMs,
getPerProjectAccounts,
getTelemetryEnabled,
getEmptyResponseMaxRetries,
getEmptyResponseRetryDelayMs,
getPidOffsetEnabled,
Expand Down Expand Up @@ -99,6 +100,7 @@ import {
setCorrelationId,
clearCorrelationId,
} from "./lib/logger.js";
import { recordTelemetryEvent } from "./lib/telemetry.js";
import { checkAndNotify } from "./lib/auto-update-checker.js";
import { handleContextOverflow } from "./lib/context-overflow.js";
import {
Expand Down Expand Up @@ -1159,6 +1161,20 @@ export const OpenAIOAuthPlugin: Plugin = async ({ client }: PluginInput) => {
const emptyResponseMaxRetries = getEmptyResponseMaxRetries(pluginConfig);
const emptyResponseRetryDelayMs = getEmptyResponseRetryDelayMs(pluginConfig);
const pidOffsetEnabled = getPidOffsetEnabled(pluginConfig);
const telemetryEnabled = getTelemetryEnabled(pluginConfig);
const emitPluginTelemetry = (
event: string,
outcome: "start" | "success" | "failure" | "recovery" | "info",
details?: Record<string, unknown>,
): void => {
if (!telemetryEnabled) return;
void recordTelemetryEvent({
source: "plugin",
event,
outcome,
details,
});
};
const effectiveUserConfig = fastSessionEnabled
? applyFastSessionDefaults(userConfig)
: userConfig;
Expand Down Expand Up @@ -1503,6 +1519,12 @@ while (attempted.size < Math.max(1, accountCount)) {
runtimeMetrics.failedRequests++;
runtimeMetrics.accountRotations++;
runtimeMetrics.lastError = (err as Error)?.message ?? String(err);
emitPluginTelemetry("request.auth_refresh_failed", "failure", {
accountIndex: account.index + 1,
modelFamily,
model,
error: (err as Error)?.message ?? String(err),
});
const failures = accountManager.incrementAuthFailures(account);
const accountLabel = formatAccountLabel(account, account.index);

Expand Down Expand Up @@ -1693,6 +1715,12 @@ while (attempted.size < Math.max(1, accountCount)) {
runtimeMetrics.networkErrors++;
runtimeMetrics.accountRotations++;
runtimeMetrics.lastError = errorMsg;
emitPluginTelemetry("request.network_error", "failure", {
accountIndex: account.index + 1,
modelFamily,
model,
error: errorMsg,
});
const policy = evaluateFailurePolicy(
{ kind: "network", failoverMode },
{ networkCooldownMs: networkErrorCooldownMs },
Expand Down Expand Up @@ -1961,6 +1989,12 @@ while (attempted.size < Math.max(1, accountCount)) {
runtimeMetrics.serverErrors++;
runtimeMetrics.accountRotations++;
runtimeMetrics.lastError = `HTTP ${response.status}`;
emitPluginTelemetry("request.server_error", "failure", {
accountIndex: account.index + 1,
modelFamily,
model,
status: response.status,
});
const serverRetryAfterMs = parseRetryAfterHintMs(response.headers);
const policy = evaluateFailurePolicy(
{ kind: "server", failoverMode, serverRetryAfterMs: serverRetryAfterMs ?? undefined },
Expand Down Expand Up @@ -2080,6 +2114,12 @@ while (attempted.size < Math.max(1, accountCount)) {
}
runtimeMetrics.failedRequests++;
runtimeMetrics.lastError = `HTTP ${response.status}`;
emitPluginTelemetry("request.http_error", "failure", {
accountIndex: account.index + 1,
modelFamily,
model,
status: response.status,
});
return errorResponse;
}

Expand Down Expand Up @@ -2243,6 +2283,14 @@ while (attempted.size < Math.max(1, accountCount)) {
`Recovered stream via failover attempt ${failoverAttempt} using account ${fallbackAccount.index + 1}.`,
{ emittedBytes },
);
emitPluginTelemetry("request.stream_failover_recovered", "recovery", {
accountIndex: fallbackAccount.index + 1,
fromAccountIndex: account.index + 1,
failoverAttempt,
emittedBytes,
modelFamily,
model,
});
return fallbackResponse;
} catch (streamFailoverError) {
accountManager.refundToken(fallbackAccount, modelFamily, model);
Expand All @@ -2257,10 +2305,21 @@ while (attempted.size < Math.max(1, accountCount)) {
emittedBytes,
error:
streamFailoverError instanceof Error
? streamFailoverError.message
: String(streamFailoverError),
? streamFailoverError.message
: String(streamFailoverError),
},
);
emitPluginTelemetry("request.stream_failover_attempt_failed", "failure", {
accountIndex: fallbackAccount.index + 1,
failoverAttempt,
emittedBytes,
modelFamily,
model,
error:
streamFailoverError instanceof Error
? streamFailoverError.message
: String(streamFailoverError),
});
continue;
} finally {
clearTimeout(fallbackTimeoutId);
Expand Down Expand Up @@ -2351,6 +2410,14 @@ while (attempted.size < Math.max(1, accountCount)) {
);
runtimeMetrics.successfulRequests++;
runtimeMetrics.lastError = null;
if (sameAccountRetryCount > 0) {
emitPluginTelemetry("request.recovered_after_retry", "recovery", {
accountIndex: successAccountForResponse.index + 1,
retryCount: sameAccountRetryCount,
modelFamily,
model,
});
}
if (lastCodexCliActiveSyncIndex !== successAccountForResponse.index) {
void accountManager.syncCodexCliActiveSelectionForIndex(successAccountForResponse.index);
lastCodexCliActiveSyncIndex = successAccountForResponse.index;
Expand Down Expand Up @@ -2392,6 +2459,13 @@ while (attempted.size < Math.max(1, accountCount)) {
: `All ${count} account(s) failed (server errors or auth issues). Check account health with \`codex-health\`.`;
runtimeMetrics.failedRequests++;
runtimeMetrics.lastError = message;
emitPluginTelemetry("request.accounts_exhausted", "failure", {
accountCount: count,
waitMs,
modelFamily,
model,
status: waitMs > 0 ? 429 : 503,
});
return new Response(JSON.stringify({ error: { message } }), {
status: waitMs > 0 ? 429 : 503,
headers: {
Expand Down
Loading