diff --git a/apps/server/src/provider/CodexProvider.ts b/apps/server/src/provider/CodexProvider.ts index f3bbf514a83e1..e76e079ff4fbb 100644 --- a/apps/server/src/provider/CodexProvider.ts +++ b/apps/server/src/provider/CodexProvider.ts @@ -125,8 +125,9 @@ const CODEX_CATALOG_REFRESH_TIMEOUT_MS = 3_000; const CODEX_CONFIG_PROBE_TIMEOUT_MS = 3_000; const CODEX_CATALOG_REFRESH_POLL = "100 millis" as const; const CODEX_CLIENT_NAME = "T3 Code"; -// Handshake plus account/models/skills requests can exceed the shared 10s probe budget. -const CODEX_AUTH_PROBE_TIMEOUT_MS = 20_000; +// Old machines can take ~25s to start app-server and answer initialize, before +// account/models/skills discovery runs. +export const CODEX_APP_SERVER_PROBE_TIMEOUT_MS = 60_000; const CODEX_DISCOVERY_MARGIN_MS = 250; // `codex-cli 0.159.0`, `codex-cli 0.160.0-alpha.1` or the npm CLI's bare `0.1.4161006`. // Keep a prerelease suffix so compatibility stays unknown for it, as it does for `initialize`. @@ -953,7 +954,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu const probeTimeoutMs = resolveProviderProbeTimeoutMs( "codex", - CODEX_AUTH_PROBE_TIMEOUT_MS, + CODEX_APP_SERVER_PROBE_TIMEOUT_MS, resolvedEnvironment, ); const probeResult = yield* probe({ diff --git a/apps/server/src/provider/Drivers/CodexDriver.ts b/apps/server/src/provider/Drivers/CodexDriver.ts index f8a753169b54a..48f1d0a96c5be 100644 --- a/apps/server/src/provider/Drivers/CodexDriver.ts +++ b/apps/server/src/provider/Drivers/CodexDriver.ts @@ -28,6 +28,7 @@ import { ProviderDriverError } from "../Errors.ts"; import * as ResetCreditCoordinator from "../resetCreditCoordinator.ts"; import { checkCodexProviderStatus, + CODEX_APP_SERVER_PROBE_TIMEOUT_MS, makePendingCodexProvider, probeCodexSkillsForCwd, withCodexAppServerClient, @@ -334,7 +335,13 @@ export const CodexDriver: ProviderDriver< environment: processEnv, }).pipe( Effect.scoped, - Effect.timeout(resolveProviderProbeTimeoutMs("codex", 20_000, processEnv)), + Effect.timeout( + resolveProviderProbeTimeoutMs( + "codex", + CODEX_APP_SERVER_PROBE_TIMEOUT_MS, + processEnv, + ), + ), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), ), ]).pipe( diff --git a/apps/server/src/provider/ProviderRegistry.test.ts b/apps/server/src/provider/ProviderRegistry.test.ts index 564a90f40e6ce..1fc7134880248 100644 --- a/apps/server/src/provider/ProviderRegistry.test.ts +++ b/apps/server/src/provider/ProviderRegistry.test.ts @@ -509,10 +509,10 @@ it.layer( Effect.gen(function* () { const fiber = yield* checkCodexProviderStatus( defaultCodexSettings, - () => Effect.sleep("22 seconds").pipe(Effect.as(makeCodexProbeSnapshot())), - { T3CODE_CODEX_PROBE_TIMEOUT_MS: "60000" }, + () => Effect.sleep("65 seconds").pipe(Effect.as(makeCodexProbeSnapshot())), + { T3CODE_CODEX_PROBE_TIMEOUT_MS: "90000" }, ).pipe(Effect.forkChild); - yield* TestClock.adjust("22 seconds"); + yield* TestClock.adjust("65 seconds"); const status = yield* Fiber.join(fiber); assert.strictEqual(status.status, "ready"); assert.strictEqual(status.auth.status, "authenticated"); @@ -604,6 +604,35 @@ it.layer( }), ); + it.effect("returns ready when app-server answers slowly but correctly", () => + Effect.gen(function* () { + const fiber = yield* checkCodexProviderStatus(defaultCodexSettings, () => + Effect.sleep("25 seconds").pipe(Effect.as(makeCodexProbeSnapshot())), + ).pipe(Effect.forkChild); + yield* TestClock.adjust("25 seconds"); + const status = yield* Fiber.join(fiber); + + assert.strictEqual(status.message, undefined); + assert.strictEqual(status.status, "ready"); + }), + ); + + it.effect("times out when app-server never answers", () => + Effect.gen(function* () { + const fiber = yield* checkCodexProviderStatus(defaultCodexSettings, () => + Effect.never.pipe(Effect.as(makeCodexProbeSnapshot())), + ).pipe(Effect.forkChild); + yield* TestClock.adjust("60 seconds"); + const status = yield* Fiber.join(fiber); + + assert.strictEqual(status.status, "error"); + assert.strictEqual( + status.message, + "Timed out while checking Codex app-server provider status.", + ); + }), + ); + it.effect("offers cyber access only on catalog models that advertise it", () => Effect.gen(function* () { const catalogModel = (slug: string, cyber?: Array<"standard" | "daybreakBlue">) => ({ diff --git a/docs/user/install.md b/docs/user/install.md index b594894efe377..71ffdae2edf0f 100644 --- a/docs/user/install.md +++ b/docs/user/install.md @@ -251,16 +251,16 @@ For provider-specific setup and accounts, see [Codex](./providers-codex.md), If a provider works in its CLI but T3 Code reports a timeout while checking it, allow more time in the T3 server's environment. For example, give Codex and -Claude readiness checks 60 seconds: +Claude readiness checks 120 seconds: ```bash -T3CODE_CODEX_PROBE_TIMEOUT_MS=60000 T3CODE_CLAUDE_PROBE_TIMEOUT_MS=60000 t3 serve +T3CODE_CODEX_PROBE_TIMEOUT_MS=120000 T3CODE_CLAUDE_PROBE_TIMEOUT_MS=120000 t3 serve ``` For a remote environment, set these on the machine running the T3 server. Keep them in your launcher or service configuration to retain them across updates, then restart the server. For systemd, add -`Environment=T3CODE_CODEX_PROBE_TIMEOUT_MS=60000` under `[Service]` in a service +`Environment=T3CODE_CODEX_PROBE_TIMEOUT_MS=120000` under `[Service]` in a service override, reload systemd, and restart the service. Provider instances can also set their own overrides in Settings → Providers.