* Main entry point: run codex with retry logic for transient API failures. * Codex does not support --continue session resumption, so all retries are fresh runs.
()
| 351 | * Codex does not support --continue session resumption, so all retries are fresh runs. |
| 352 | */ |
| 353 | async function main() { |
| 354 | const [, , command, ...args] = process.argv; |
| 355 | |
| 356 | if (!command) { |
| 357 | process.stderr.write("codex-harness: Usage: node codex_harness.cjs <command> [args...]\n"); |
| 358 | process.exit(1); |
| 359 | } |
| 360 | |
| 361 | log(`starting: command=${command} maxRetries=${MAX_RETRIES} initialDelayMs=${INITIAL_DELAY_MS}` + ` backoffMultiplier=${BACKOFF_MULTIPLIER} maxDelayMs=${MAX_DELAY_MS}` + ` nodeVersion=${process.version} platform=${process.platform}`); |
| 362 | |
| 363 | // Pre-flight: skip the agent entirely when a noop has already been written by a prior step. |
| 364 | // A noop indicates the work is complete or there is nothing to do — starting the agent |
| 365 | // would be wasteful and potentially harmful. This check runs before API key validation so |
| 366 | // that a noop can be honoured even when credentials are absent. |
| 367 | const safeOutputsPath = process.env.GH_AW_SAFE_OUTPUTS || ""; |
| 368 | if (safeOutputsPath && hasNoopInSafeOutputs(safeOutputsPath, { logger: log })) { |
| 369 | log("pre-flight: noop message found in safe-outputs — skipping agent (work is already complete or no work needed)"); |
| 370 | process.exit(0); |
| 371 | } |
| 372 | |
| 373 | // Diagnose API key presence so CI failures can be triaged without exposing secret values. |
| 374 | const codexApiKey = process.env.CODEX_API_KEY; |
| 375 | const openaiApiKey = process.env.OPENAI_API_KEY; |
| 376 | const codexChildEnv = buildCodexChildEnv(process.env, codexApiKey, openaiApiKey); |
| 377 | log(`secrets: CODEX_API_KEY=${codexApiKey ? `set (length=${codexApiKey.length})` : "not set"}` + ` OPENAI_API_KEY=${openaiApiKey ? `set (length=${openaiApiKey.length})` : "not set"}`); |
| 378 | |
| 379 | // Pre-flight: require at least one API key before spawning codex. |
| 380 | // Without a key, codex exits immediately with "Missing environment variable" and every |
| 381 | // retry attempt fails the same way. Failing here avoids burning the retry budget and |
| 382 | // surfaces a clear, actionable message in CI logs. |
| 383 | if (!codexApiKey && !openaiApiKey) { |
| 384 | log("fatal: no API key available - set CODEX_API_KEY or OPENAI_API_KEY and retry"); |
| 385 | process.exit(1); |
| 386 | } |
| 387 | |
| 388 | // Resolve the prompt for the initial run (reads --prompt-file content). |
| 389 | // A missing or unreadable prompt file is treated as a fatal startup error. |
| 390 | let resolvedArgs; |
| 391 | try { |
| 392 | resolvedArgs = resolveCodexPromptFileArgs(args); |
| 393 | } catch (err) { |
| 394 | const e = /** @type {Error} */ err; |
| 395 | log(`fatal: ${e.message}`); |
| 396 | process.exit(1); |
| 397 | } |
| 398 | |
| 399 | // Safe arg list for logging: when --prompt-file was present, the last element of |
| 400 | // resolvedArgs is the resolved prompt content. Replace it with a placeholder so that |
| 401 | // task instructions are never written to stderr or captured in agent logs. |
| 402 | const hadPromptFile = args.includes("--prompt-file"); |
| 403 | const safeArgs = hadPromptFile && resolvedArgs.length > 0 ? [...resolvedArgs.slice(0, -1), "<prompt omitted>"] : resolvedArgs; |
| 404 | |
| 405 | // Inject --json after `exec` to stream structured JSONL events to stdout, making |
| 406 | // Codex output machine-readable in CI without affecting the stderr progress stream. |
| 407 | resolvedArgs = injectJsonFlag(resolvedArgs); |
| 408 | |
| 409 | // Fetch AWF API proxy reflection data before running the agent to capture initial proxy state. |
| 410 | // This is best-effort: failures are logged but do not affect the agent run. |
no test coverage detected