Skip to content

Commit e99d641

Browse files
committed
release: ship v1.2.6 forwarded observability fix
Record forwarded Codex request traffic in runtime observability when the child process leaves the snapshot unchanged, and publish the 1.2.6 release notes and version metadata.
1 parent a76b67c commit e99d641

File tree

7 files changed

+420
-13
lines changed

7 files changed

+420
-13
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,8 @@ codex auth doctor --json
321321

322322
## Release Notes
323323

324-
- Current stable: [docs/releases/v1.2.5.md](docs/releases/v1.2.5.md)
325-
- Recent patch release: [docs/releases/v1.2.4.md](docs/releases/v1.2.4.md)
324+
- Current stable: [docs/releases/v1.2.6.md](docs/releases/v1.2.6.md)
325+
- Recent patch release: [docs/releases/v1.2.5.md](docs/releases/v1.2.5.md)
326326
- Previous stable: [docs/releases/v1.2.2.md](docs/releases/v1.2.2.md)
327327
- Earlier stable: [docs/releases/v1.2.1.md](docs/releases/v1.2.1.md)
328328
- Archived prerelease: [docs/releases/v0.1.0-beta.0.md](docs/releases/v0.1.0-beta.0.md)

docs/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ Public documentation for `codex-multi-auth`.
2323
| [configuration.md](configuration.md) | Stable defaults, precedence, and environment overrides |
2424
| [architecture.md](architecture.md) | Public system overview of the wrapper, storage, and optional plugin runtime |
2525
| [privacy.md](privacy.md) | Data handling and local storage behavior |
26-
| [releases/v1.2.5.md](releases/v1.2.5.md) | Stable release notes |
27-
| [releases/v1.2.4.md](releases/v1.2.4.md) | Previous stable release notes |
26+
| [releases/v1.2.6.md](releases/v1.2.6.md) | Stable release notes |
27+
| [releases/v1.2.5.md](releases/v1.2.5.md) | Previous stable release notes |
28+
| [releases/v1.2.4.md](releases/v1.2.4.md) | Earlier stable release notes |
2829
| [releases/v1.2.2.md](releases/v1.2.2.md) | Earlier stable release notes |
2930
| [releases/v1.2.1.md](releases/v1.2.1.md) | Earlier stable release notes |
3031
| [releases/v1.2.0.md](releases/v1.2.0.md) | Archived stable release notes |
@@ -53,7 +54,7 @@ Public documentation for `codex-multi-auth`.
5354
| [reference/storage-paths.md](reference/storage-paths.md) | Canonical and compatibility storage paths |
5455
| [reference/public-api.md](reference/public-api.md) | Public API stability and semver contract |
5556
| [reference/error-contracts.md](reference/error-contracts.md) | CLI, JSON, and helper error semantics |
56-
| [releases/v1.2.5.md](releases/v1.2.5.md) | Current stable release notes |
57+
| [releases/v1.2.6.md](releases/v1.2.6.md) | Current stable release notes |
5758
| [releases/v0.1.0-beta.0.md](releases/v0.1.0-beta.0.md) | Archived prerelease reference |
5859
| [Daily Use release notes](#daily-use) | Stable, previous, and archived release notes |
5960
| [releases/legacy-pre-0.1-history.md](releases/legacy-pre-0.1-history.md) | Archived pre-0.1 changelog history |

docs/releases/v1.2.6.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Release v1.2.6
2+
3+
Release line: `stable`
4+
5+
This patch release fixes forwarded `codex exec` runtime observability so real wrapped Codex requests are reflected in `codex auth status` and `codex auth report --json` even when the downstream Codex runtime does not update the snapshot itself.
6+
7+
## Scope
8+
9+
- Published package version: `1.2.6`
10+
- Previous stable release: `v1.2.5`
11+
12+
## What Changed
13+
14+
- fixed wrapped non-auth Codex commands so successful forwarded request traffic increments persisted runtime observability counters
15+
- added a wrapper fallback path that records forwarded request metrics only when the child Codex process leaves the runtime snapshot unchanged, avoiding double-counting when plugin-side metrics are present
16+
- preserved the existing `codex auth` observability behavior while making real `codex exec` smoke runs visible in `codex auth status` and `codex auth report --json`
17+
- added regression coverage for both missing-child-update and already-updated snapshot cases in the wrapper test suite
18+
19+
## Validation
20+
21+
- `npm test -- test/codex-bin-wrapper.test.ts`
22+
- `npm run build`
23+
- `codex -a never exec --sandbox read-only --color never -o "$env:TEMP\\codex-smoke-last.txt" "Reply with exactly OK and nothing else."`
24+
- `codex auth status`
25+
- `codex auth report --json`
26+
27+
## Related
28+
29+
- Previous release notes: `docs/releases/v1.2.5.md`

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codex-multi-auth",
3-
"version": "1.2.5",
3+
"version": "1.2.6",
44
"description": "Multi-account OAuth manager and codex auth wrapper for the official @openai/codex CLI, with switching, health checks, and recovery tools",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",

scripts/codex.js

Lines changed: 190 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,189 @@ function resolveOriginalMultiAuthDir(env) {
661661
return undefined;
662662
}
663663

664+
async function loadRuntimeObservabilityModule() {
665+
try {
666+
const mod = await import("../dist/lib/runtime/runtime-observability.js");
667+
if (
668+
typeof mod.loadPersistedRuntimeObservabilitySnapshot !== "function" ||
669+
typeof mod.mutateRuntimeObservabilitySnapshot !== "function"
670+
) {
671+
return null;
672+
}
673+
return mod;
674+
} catch (error) {
675+
if (
676+
error &&
677+
typeof error === "object" &&
678+
"code" in error &&
679+
error.code === "ERR_MODULE_NOT_FOUND"
680+
) {
681+
return null;
682+
}
683+
throw error;
684+
}
685+
}
686+
687+
function isPureHelpOrVersionArgs(rawArgs) {
688+
if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
689+
return false;
690+
}
691+
return rawArgs.every((arg) =>
692+
typeof arg === "string" && ["--help", "-h", "--version", "-V"].includes(arg),
693+
);
694+
}
695+
696+
function consumesNextArg(arg) {
697+
return new Set([
698+
"-c",
699+
"--config",
700+
"--enable",
701+
"--disable",
702+
"--remote",
703+
"--remote-auth-token-env",
704+
"-i",
705+
"--image",
706+
"-m",
707+
"--model",
708+
"--local-provider",
709+
"-p",
710+
"--profile",
711+
"-s",
712+
"--sandbox",
713+
"-a",
714+
"--ask-for-approval",
715+
"-C",
716+
"--cd",
717+
"--add-dir",
718+
"--output-schema",
719+
"--color",
720+
"-o",
721+
"--output-last-message",
722+
]).has(arg);
723+
}
724+
725+
function shouldTrackForwardedRuntimeObservability(rawArgs) {
726+
if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
727+
return true;
728+
}
729+
if (isPureHelpOrVersionArgs(rawArgs)) {
730+
return false;
731+
}
732+
733+
const requestCommands = new Set(["exec", "review", "resume", "fork"]);
734+
const nonRequestCommands = new Set([
735+
"help",
736+
"completion",
737+
"login",
738+
"logout",
739+
"mcp",
740+
"mcp-server",
741+
"app-server",
742+
"sandbox",
743+
"debug",
744+
"apply",
745+
"cloud",
746+
"features",
747+
"auth",
748+
]);
749+
750+
for (let i = 0; i < rawArgs.length; i += 1) {
751+
const arg = rawArgs[i];
752+
if (typeof arg !== "string" || arg.length === 0) continue;
753+
if (arg === "--") {
754+
return i + 1 < rawArgs.length;
755+
}
756+
if (arg.startsWith("--config=")) {
757+
continue;
758+
}
759+
if (arg.startsWith("--") || (arg.startsWith("-") && arg !== "-")) {
760+
if (consumesNextArg(arg)) {
761+
i += 1;
762+
}
763+
continue;
764+
}
765+
if (requestCommands.has(arg)) {
766+
return true;
767+
}
768+
if (nonRequestCommands.has(arg)) {
769+
return false;
770+
}
771+
return true;
772+
}
773+
774+
return true;
775+
}
776+
777+
function createRuntimeSnapshotChangeToken(snapshot) {
778+
return JSON.stringify({
779+
updatedAt: snapshot?.updatedAt ?? null,
780+
responsesRequests: snapshot?.responsesRequests ?? null,
781+
authRefreshRequests: snapshot?.authRefreshRequests ?? null,
782+
diagnosticProbeRequests: snapshot?.diagnosticProbeRequests ?? null,
783+
totalRequests: snapshot?.runtimeMetrics?.totalRequests ?? null,
784+
successfulRequests: snapshot?.runtimeMetrics?.successfulRequests ?? null,
785+
failedRequests: snapshot?.runtimeMetrics?.failedRequests ?? null,
786+
lastRequestAt: snapshot?.runtimeMetrics?.lastRequestAt ?? null,
787+
});
788+
}
789+
790+
async function loadRuntimeSnapshotWithRetry(runtimeObservabilityModule) {
791+
let snapshot = null;
792+
for (let attempt = 0; attempt < 5; attempt += 1) {
793+
snapshot =
794+
(await runtimeObservabilityModule.loadPersistedRuntimeObservabilitySnapshot()) ??
795+
null;
796+
if (snapshot) {
797+
return snapshot;
798+
}
799+
if (attempt < 4) {
800+
await new Promise((resolve) => setTimeout(resolve, 20));
801+
}
802+
}
803+
return snapshot;
804+
}
805+
806+
async function withForwardedRuntimeObservability(rawArgs, runForwardedCommand) {
807+
if (!shouldTrackForwardedRuntimeObservability(rawArgs)) {
808+
return runForwardedCommand();
809+
}
810+
811+
const runtimeObservabilityModule = await loadRuntimeObservabilityModule();
812+
if (!runtimeObservabilityModule) {
813+
return runForwardedCommand();
814+
}
815+
816+
const beforeSnapshot = await loadRuntimeSnapshotWithRetry(runtimeObservabilityModule);
817+
const beforeToken = createRuntimeSnapshotChangeToken(beforeSnapshot);
818+
const startedAt = Date.now();
819+
const exitCode = await runForwardedCommand();
820+
const afterSnapshot = await loadRuntimeSnapshotWithRetry(runtimeObservabilityModule);
821+
const afterToken = createRuntimeSnapshotChangeToken(afterSnapshot);
822+
if (afterToken !== beforeToken) {
823+
return exitCode;
824+
}
825+
826+
runtimeObservabilityModule.mutateRuntimeObservabilitySnapshot((snapshot) => {
827+
snapshot.currentRequestId = null;
828+
snapshot.responsesRequests += 1;
829+
snapshot.runtimeMetrics.totalRequests += 1;
830+
snapshot.runtimeMetrics.responsesRequests += 1;
831+
snapshot.runtimeMetrics.cumulativeLatencyMs += Math.max(0, Date.now() - startedAt);
832+
snapshot.runtimeMetrics.lastRequestAt = Date.now();
833+
if (!snapshot.runtimeMetrics.startedAt) {
834+
snapshot.runtimeMetrics.startedAt = startedAt;
835+
}
836+
if (exitCode === 0) {
837+
snapshot.runtimeMetrics.successfulRequests += 1;
838+
snapshot.runtimeMetrics.lastError = null;
839+
} else {
840+
snapshot.runtimeMetrics.failedRequests += 1;
841+
snapshot.runtimeMetrics.lastError = `forwarded-codex-exit:${exitCode}`;
842+
}
843+
});
844+
return exitCode;
845+
}
846+
664847
function createCompatibilityCodexHome(
665848
processedArgs,
666849
requestedModel,
@@ -1149,11 +1332,13 @@ async function main() {
11491332
forwardArgs,
11501333
requestedModel,
11511334
);
1152-
return forwardToRealCodex(
1153-
realCodexBin,
1154-
compatibility.args,
1155-
compatibility.env,
1156-
compatibility.cleanup,
1335+
return withForwardedRuntimeObservability(rawArgs, () =>
1336+
forwardToRealCodex(
1337+
realCodexBin,
1338+
compatibility.args,
1339+
compatibility.env,
1340+
compatibility.cleanup,
1341+
),
11571342
);
11581343
}
11591344

0 commit comments

Comments
 (0)