Skip to content
Source
Sign inSign up

[Bug]: Codex configured for Google Vertex AI fails account discovery #35852

Send to my agent
Opencoygeek(GitHub profile, opens in a new tab) opened this issue 0 comments

Summary

Codex configured for Google Vertex AI fails T3 Code's required account discovery because the app-server response uses an account type that T3's decoder rejects. This prevents the configured Codex provider from completing discovery even though it requires no OpenAI login. An isolated, credential-free app-server run reproduced the rejected response; an existing non-OpenAI-auth account type passed the same decoder as a control.

Before submitting

  • Related issues: listed under Additional context; no matching GCP account-discovery report found in open and closed issues or pull requests.
  • Removed credentials, tokens, private paths, and sensitive data.

Area

apps/server

Observed behavior

With model_provider = "gcp" in an isolated Codex configuration, the real app server returned {"account":{"type":"gcp"},"requiresOpenaiAuth":false,"workspaceRouting":null} for account/read. T3 Code's actual decodeOptionalPayload helper and generated V2GetAccountResponse schema rejected it. The same schema accepted a no-OpenAI-auth Amazon Bedrock control. The account-probe failure and its provider-error consequence are also established by the source path cited below; no T3 UI session or inference turn was run.

Reproduction

  1. Save the script below as repro-gcp.sh.
  2. Run sh repro-gcp.sh /tmp/t3code-checkout against a clean checkout of the target commit.
  3. Observe the accepted Bedrock control, the real GCP wire response, and the failed T3 decoder.

The failure reproduced once in one isolated app-server run. No credentials, login, external auth helper, or inference request is needed.

Prerequisites: POSIX sh, Node.js with native TypeScript loading, npm, Git, Codex CLI, and network access to install the repository-pinned Effect dependency. The script creates an empty workspace and isolated HOME, CODEX_HOME, XDG directories, T3CODE_HOME, and TMPDIR under a temporary directory, installs only Effect with lifecycle scripts disabled, calls initialize and account/read, and removes its temporary data on exit. It leaves the checkout unchanged. Exit 1 means the defect reproduced, exit 0 means the GCP response was accepted, and exit 2 means setup or the control was inconclusive.

Reproduction script: repro-gcp.sh
#!/bin/sh
set -u
repo=$(CDPATH= cd -- "$1" && pwd) || exit 2
scratch=$(mktemp -d /tmp/codex-gcp-repro.XXXXXX) || exit 2
trap 'rm -rf "$scratch"' EXIT HUP INT TERM
export HOME="$scratch/home" CODEX_HOME="$scratch/codex"
export XDG_CONFIG_HOME="$scratch/config" XDG_DATA_HOME="$scratch/data" XDG_CACHE_HOME="$scratch/cache"
export T3CODE_HOME="$scratch/t3" TMPDIR="$scratch/tmp"
mkdir -p "$HOME" "$CODEX_HOME" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_CACHE_HOME" "$T3CODE_HOME" "$TMPDIR" "$scratch/workspace"
unset OPENAI_API_KEY OPENAI_BASE_URL CODEX_API_KEY CHATGPT_ACCESS_TOKEN GOOGLE_APPLICATION_CREDENTIALS
node --version
npm --version
codex --version
git -C "$repo" rev-parse HEAD
if ! npm install --prefix "$scratch" --no-save --no-package-lock --ignore-scripts --no-audit --no-fund effect@4.1.3 > "$scratch/install.log" 2>&1; then
  cat "$scratch/install.log"
  echo "RESULT: inconclusive"
  exit 2
fi
printf 'model_provider = "gcp"\ncli_auth_credentials_store = "file"\n' > "$CODEX_HOME/config.toml"
cat > "$scratch/check.mjs" <<'JS'
import { registerHooks, createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import { spawn } from "node:child_process";
import { createInterface } from "node:readline";
import { resolve } from "node:path";
const [repo, scratch] = process.argv.slice(2);
const require = createRequire(import.meta.url);
const effectRoot = resolve(scratch, "node_modules/effect/dist");
registerHooks({ resolve(specifier, context, nextResolve) {
  if (specifier.startsWith("effect/")) {
    return nextResolve(pathToFileURL(resolve(effectRoot, specifier.slice(7) + ".js")).href, context);
  }
  return nextResolve(specifier, context);
}});
const Schema = await import("effect/Schema");
const Effect = await import("effect/Effect");
const Exit = await import("effect/Exit");
const CodexSchema = await import(pathToFileURL(resolve(repo, "packages/effect-codex-app-server/src/_generated/schema.gen.ts")));
const { decodeOptionalPayload } = await import(pathToFileURL(resolve(repo, "packages/effect-codex-app-server/src/_internal/shared.ts")));
console.log("effect " + require("./node_modules/effect/package.json").version);
const control = { account: { type: "amazonBedrock", usesCodexManagedCredentials: false }, requiresOpenaiAuth: false, workspaceRouting: null };
if (!Schema.is(CodexSchema.V2GetAccountResponse)(control)) throw new Error("Bedrock control failed");
console.log("CONTROL: no-OpenAI-auth Bedrock account accepted");
const env = {};
for (const key of ["PATH", "HOME", "CODEX_HOME", "XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_CACHE_HOME", "T3CODE_HOME", "TMPDIR"]) env[key] = process.env[key];
const child = spawn("codex", ["app-server"], { cwd: resolve(scratch, "workspace"), env, stdio: ["pipe", "pipe", "pipe"] });
child.stderr.resume();
const pending = new Map();
const lines = createInterface({ input: child.stdout });
lines.on("line", line => {
  let message;
  try { message = JSON.parse(line); } catch { return; }
  const waiter = pending.get(message.id);
  if (!waiter) return;
  pending.delete(message.id);
  if (message.error) waiter.reject(new Error(JSON.stringify(message.error))); else waiter.resolve(message.result);
});
child.on("error", error => { for (const waiter of pending.values()) waiter.reject(error); });
child.on("exit", () => { for (const waiter of pending.values()) waiter.reject(new Error("app-server exited")); });
let id = 0;
const request = (method, params) => new Promise((resolve, reject) => {
  const requestId = ++id; pending.set(requestId, { resolve, reject });
  child.stdin.write(JSON.stringify({ id: requestId, method, params }) + "\n");
});
const timeout = setTimeout(() => {
  for (const waiter of pending.values()) waiter.reject(new Error("app-server response timed out"));
}, 15000);
try {
  const init = await request("initialize", { clientInfo: { name: "gcp-repro", version: "1" }, capabilities: { experimentalApi: true } });
  child.stdin.write(JSON.stringify({ method: "initialized" }) + "\n");
  const account = await request("account/read", {});
  console.log("WIRE: " + JSON.stringify(account));
  if (account.account?.type !== "gcp" || account.requiresOpenaiAuth !== false) throw new Error("Unexpected GCP response");
  const decoded = await Effect.runPromiseExit(decodeOptionalPayload("account/read", CodexSchema.V2GetAccountResponse, account));
  const accepted = Exit.isSuccess(decoded);
  console.log("T3 account/read decoder: " + (accepted ? "accepted" : "rejected"));
  console.log(accepted ? "RESULT: expected behavior" : "RESULT: defect reproduced");
  process.exitCode = accepted ? 0 : 1;
} catch (error) {
  console.log("SETUP: " + error.message);
  console.log("RESULT: inconclusive");
  process.exitCode = 2;
} finally {
  clearTimeout(timeout);
  lines.close();
  child.kill("SIGKILL");
}
JS
node "$scratch/check.mjs" "$repo" "$scratch"

Expected behavior

A GCP-configured Codex provider should complete account discovery when Codex reports account.type = "gcp" and requiresOpenaiAuth = false. This is a defined account variant in the release-matched upstream account protocol, account.rs#L16-L47. The release-matched credential-free account test, account_gcp.rs#L1-L56 asserts exactly this response without resolving GCP credentials. Both stable and experimental JSON-schema exports from the tested Codex binary contain the GCP variant.

Impact and scope

Impact: P1? - The required account-discovery boundary rejects the configured GCP provider before model discovery can finish and produces a provider error. This is a potential blocker for the selected GCP route; the rating is provisional because an end-to-end T3 turn was not exercised.

The reproduced scope is Codex's GCP account response at the decoding boundary. The source shows the unconditional account read and failure-to-provider-error path. No working T3 workaround was established; whether a manually configured custom model can bypass the provider-error state is NOT_ENOUGH_INFO and needs a separate end-to-end T3 check. Authenticated GCP inference and UI rendering were not exercised.

Version

T3 Code commit 59e64eef881632a23f89c4138bcf37f23d92bf93; tested Codex output codex-cli 0.167.2. Tagged account definitions at the managed Codex baseline also include GCP, while the generator's pinned source predates that variant.

Runtime and platform

macOS 27.2, Darwin arm64; Node.js v26.12.1; npm 11.20.0; Effect 4.1.3, matching the repository's dependency pin. Cross-platform execution was not run.

Interface

API: Codex app-server account/read, consumed by T3 Code's Codex provider probe.

Earliest failure stage

Input parsing or validation

Relevant output or evidence

The exact reproduction script produced:

v26.12.1
11.20.0
codex-cli 0.167.2
59e64eef881632a23f89c4138bcf37f23d92bf93
effect 4.1.3
CONTROL: no-OpenAI-auth Bedrock account accepted
WIRE: {"account":{"type":"gcp"},"requiresOpenaiAuth":false,"workspaceRouting":null}
T3 account/read decoder: rejected
RESULT: defect reproduced

The generated schema.gen.ts#L20625-L20658 accepts only apiKey, chatgpt, and amazonBedrock account variants. The response is decoded by client.ts#L238-L253. The required CodexProvider.ts#L695-L718 account read precedes model discovery; CodexProvider.ts#L975-L1010 converts the failed probe to a provider error.

Additional context

  • https://github.com/pingdotgg/t3code/issues/8292 — historical strict plan-value rejection; its scope and closure concern ChatGPT plan strings, whereas this report concerns the distinct GCP account discriminator.
  1. tanstack-source added labelsbug

Sign in to join this conversation.