From d43b26ca438f043fb279ce638ec32d56994ae6c8 Mon Sep 17 00:00:00 2001 From: default Date: Fri, 10 Apr 2026 12:48:06 +0000 Subject: [PATCH] fix: handle Codex CLI OAuth device flow in headless/SSH environments - agents.sh: validate Codex credentials after writing agent-env; warn with device flow URL when neither CODEX_OPENAI_AUTH_CODE, OPENAI_API_KEY nor GITHUB_TOKEN is set; skip gracefully instead of hanging - main.tf startup_script: exchange CODEX_OPENAI_AUTH_CODE non-interactively via 'codex login --auth-code'; print device flow URL as plain text if no credential is configured at workspace start - cli.py: add info text in Codex auth section explaining headless use and CODEX_OPENAI_AUTH_CODE behaviour before the auth method prompt - oauth-setup.md: add section 6 documenting all three Codex auth paths for headless environments and how the configurator embeds the auth code - validators.py: fix missing function signature for validate_api_key_optional - test_validators.py: add coder_admin_password to _full_config helper Closes #2 --- dev-server-provision/coder/main.tf | 27 ++++++++ dev-server-provision/configurator/cli.py | 10 +++ .../configurator/tests/test_validators.py | 1 + .../configurator/validators.py | 2 +- dev-server-provision/docs/oauth-setup.md | 64 +++++++++++++++++++ dev-server-provision/infra/agents.sh | 61 ++++++++++++++++++ 6 files changed, 164 insertions(+), 1 deletion(-) diff --git a/dev-server-provision/coder/main.tf b/dev-server-provision/coder/main.tf index bd6b55c..cf1f0e1 100644 --- a/dev-server-provision/coder/main.tf +++ b/dev-server-provision/coder/main.tf @@ -55,6 +55,33 @@ resource "coder_agent" "main" { else echo "[remotevibe] WARNING: $AGENT_ENV not found — AI agents may not be authenticated" fi + + # ── Codex CLI — non-interactive authentication ─────────────────────────── + # If CODEX_OPENAI_AUTH_CODE is set, exchange it for an access token + # non-interactively so Codex does not hang waiting for browser input. + # This is equivalent to running `codex login --device-auth` in a browser. + if [ "$${ENABLE_AGENT_CODEX:-false}" = "true" ]; then + if [ -n "$${CODEX_OPENAI_AUTH_CODE:-}" ]; then + echo "[remotevibe] Codex: completing non-interactive login with CODEX_OPENAI_AUTH_CODE …" + if command -v codex >/dev/null 2>&1; then + codex login --auth-code "$CODEX_OPENAI_AUTH_CODE" 2>&1 \ + && echo "[remotevibe] Codex: login successful." \ + || echo "[remotevibe] WARNING: Codex login with auth code failed — you may need to run 'codex login' manually." + else + echo "[remotevibe] WARNING: codex CLI not found in PATH — skipping auth." + fi + elif [ -n "$${OPENAI_API_KEY:-}" ]; then + echo "[remotevibe] Codex: using OPENAI_API_KEY (API key mode)." + elif [ -n "$${GITHUB_TOKEN:-}" ]; then + echo "[remotevibe] Codex: using GITHUB_TOKEN (GitHub OAuth mode)." + else + echo "[remotevibe] WARNING: Codex is enabled but no auth credential is set." + echo "[remotevibe] To authenticate, run one of:" + echo "[remotevibe] codex login --device-auth (ChatGPT Plus/Pro — needs browser)" + echo "[remotevibe] export OPENAI_API_KEY=sk-..." + echo "[remotevibe] Device Flow URL: https://auth.openai.com/codex/device" + fi + fi # ── Configure Coder CLI for workspace template management ──────────────── if [ -f /run/secrets/coder-token ]; then _tok=$(cat /run/secrets/coder-token | tr -d '[:space:]') diff --git a/dev-server-provision/configurator/cli.py b/dev-server-provision/configurator/cli.py index d3e5a97..b15c290 100644 --- a/dev-server-provision/configurator/cli.py +++ b/dev-server-provision/configurator/cli.py @@ -473,6 +473,16 @@ def _ask_agents(config: dict[str, Any]) -> None: ).execute().strip() if config["enable_agent_codex"]: + print( + f"\n {_BOLD}Codex CLI authentication{_RESET}\n" + f" Codex requires authentication before it can run.\n" + f" In a headless server environment the Device Flow URL is printed\n" + f" to the provisioning log — open it in your browser to complete auth.\n" + f" Alternatively, authenticate here and the auth code is embedded in\n" + f" the generated config so the workspace authenticates automatically.\n" + f" Device Flow URL (for reference): " + f"{_CYAN}https://auth.openai.com/codex/device{_RESET}\n" + ) codex_auth = inquirer.select( message="How would you like to authenticate Codex CLI?", choices=[ diff --git a/dev-server-provision/configurator/tests/test_validators.py b/dev-server-provision/configurator/tests/test_validators.py index 3e8a132..292dd19 100644 --- a/dev-server-provision/configurator/tests/test_validators.py +++ b/dev-server-provision/configurator/tests/test_validators.py @@ -155,6 +155,7 @@ def _full_config(self, **overrides): "email": "admin@example.com", "cloudflare_api_token": "a" * 40, "cloudflare_zone_id": "a" * 32, + "coder_admin_password": "securepass1", "enable_agent_copilot": False, "enable_agent_claude": False, "enable_agent_gemini": False, diff --git a/dev-server-provision/configurator/validators.py b/dev-server-provision/configurator/validators.py index b7bf262..25883f0 100644 --- a/dev-server-provision/configurator/validators.py +++ b/dev-server-provision/configurator/validators.py @@ -81,7 +81,7 @@ def validate_coder_password(value: str) -> str | bool: return True - +def validate_api_key_optional(value: str) -> str | bool: """Accept empty or any non-whitespace string.""" return True diff --git a/dev-server-provision/docs/oauth-setup.md b/dev-server-provision/docs/oauth-setup.md index 136cc3c..acd4a6c 100644 --- a/dev-server-provision/docs/oauth-setup.md +++ b/dev-server-provision/docs/oauth-setup.md @@ -243,6 +243,70 @@ or GitHub token for your chosen provider. --- +## 6. Codex CLI in headless / SSH environments + +When `ENABLE_AGENT_CODEX=true` is set and the server is provisioned via +`cloud-init` or `install.sh`, there is no interactive terminal available for +browser-based OAuth flows. The Codex CLI would hang waiting for user input +if no credential is pre-configured. + +### How authentication is resolved (in order) + +| Priority | Credential | How to obtain | +|----------|------------|---------------| +| 1 (best) | `CODEX_OPENAI_AUTH_CODE` | Run the configurator before provisioning and complete the OpenAI Device Flow there. The code is embedded in the generated config. | +| 2 | `OPENAI_API_KEY` | Create a key at | +| 3 | `GITHUB_TOKEN` | GitHub OAuth Device Flow (same as Copilot) | +| — | None set | `agents.sh` prints a warning and skips; workspace startup script prints the Device Flow URL | + +### Path 1 — pre-obtain the auth code with the configurator (recommended) + +```bash +# On your local machine, before provisioning: +python -m configurator +# → Enable Codex +# → Choose "Sign in with ChatGPT (Device Flow)" +# → Complete auth in browser +# The code is saved as CODEX_OPENAI_AUTH_CODE in cloud-init.yaml / RVSconfig.yml +``` + +At workspace start, the startup script runs: +```bash +codex login --auth-code "$CODEX_OPENAI_AUTH_CODE" +``` +This completes the PKCE exchange non-interactively — no browser needed on the +server side. + +### Path 2 — API key + +Add to `/etc/dev-server/env`: +``` +OPENAI_API_KEY=sk-... +``` + +### Path 3 — post-provisioning manual auth + +If the workspace is already running, open a terminal inside it and run: +```bash +codex login --device-auth +# Follow the URL that is printed: +# https://auth.openai.com/codex/device +``` + +### Reading the Device Flow URL from the log + +When no credential is set at provisioning time, `agents.sh` logs the following +(visible in `/var/log/dev-server-provision.log`): + +``` +[agents] WARN: Device Flow URL (for manual auth): https://auth.openai.com/codex/device +``` + +The workspace startup script also echoes the URL on first start so it appears +in the Coder workspace log panel. + +--- + ## Adding a new provider To add OAuth for a new AI agent: diff --git a/dev-server-provision/infra/agents.sh b/dev-server-provision/infra/agents.sh index 0c3580e..f070ab3 100755 --- a/dev-server-provision/infra/agents.sh +++ b/dev-server-provision/infra/agents.sh @@ -17,6 +17,7 @@ set -euo pipefail LOG_FILE="${LOG_FILE:-/var/log/dev-server-provision.log}" log() { echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] [agents] $*" | tee -a "$LOG_FILE"; } +warn() { log "WARN: $*"; } ENV_FILE="${ENV_FILE:-/etc/dev-server/env}" AGENT_ENV_FILE="/etc/dev-server/agent-env" @@ -26,6 +27,12 @@ if [[ ! -f "$ENV_FILE" ]]; then exit 0 fi +# Source the env file so we can inspect individual variable values below. +set -a +# shellcheck source=/dev/null +source "$ENV_FILE" +set +a + # --------------------------------------------------------------------------- # Extract agent-relevant keys only # --------------------------------------------------------------------------- @@ -49,3 +56,57 @@ chmod 0644 "$AGENT_ENV_FILE" log "Agent key file written ($(wc -l < "$AGENT_ENV_FILE") lines)." log "Note: AI agent CLIs are installed inside the workspace Docker image." +# --------------------------------------------------------------------------- +# Codex CLI — credential validation +# --------------------------------------------------------------------------- +# Codex CLI authenticates via one of three methods (checked in order): +# 1. CODEX_OPENAI_AUTH_CODE — pre-obtained OpenAI Device Flow auth code +# (obtained via the configurator or `codex login --device-auth`). +# The workspace startup script will exchange it non-interactively. +# 2. OPENAI_API_KEY — direct API key (billing required). +# 3. GITHUB_TOKEN — Codex can authenticate via GitHub OAuth. +# +# If none of the above are set, Codex would try to launch an interactive +# Device Flow in the terminal — which hangs silently in headless/SSH +# environments. We warn early and skip instead of letting it hang. +# --------------------------------------------------------------------------- +if [[ "${ENABLE_AGENT_CODEX:-false}" == "true" ]]; then + if [[ -n "${CODEX_OPENAI_AUTH_CODE:-}" ]]; then + log "Codex: CODEX_OPENAI_AUTH_CODE is set — workspace startup will complete" + log " non-interactive authentication via 'codex login --auth-code'." + elif [[ -n "${OPENAI_API_KEY:-}" ]]; then + log "Codex: OPENAI_API_KEY is set — Codex CLI will use API key mode." + elif [[ -n "${GITHUB_TOKEN:-}" ]]; then + log "Codex: GITHUB_TOKEN is set — Codex CLI will use GitHub OAuth mode." + else + warn "Codex is enabled but no authentication credential is configured." + warn "Codex CLI would hang waiting for interactive input in a headless environment." + warn "" + warn "To fix this, obtain an auth code BEFORE provisioning:" + warn " 1. Run the configurator: python -m configurator" + warn " 2. Select 'OpenAI Codex CLI' → 'Sign in with ChatGPT (Device Flow)'" + warn " 3. Authenticate in your browser — the configurator saves the code" + warn " as CODEX_OPENAI_AUTH_CODE in your cloud-init.yaml / RVSconfig.yml" + warn "" + warn "Alternatively, set one of these in /etc/dev-server/env and re-run:" + warn " CODEX_OPENAI_AUTH_CODE= (ChatGPT Plus/Pro plan)" + warn " OPENAI_API_KEY=sk-... (API billing plan)" + warn " GITHUB_TOKEN=ghp_... (GitHub OAuth)" + warn "" + warn "Device Flow URL (for manual auth): https://auth.openai.com/codex/device" + warn "Skipping Codex credential setup — Codex may prompt for login in the workspace." + fi +fi + +# --------------------------------------------------------------------------- +# OpenCode — log provider info +# --------------------------------------------------------------------------- +if [[ "${ENABLE_AGENT_OPENCODE:-false}" == "true" ]]; then + if [[ -n "${OPENCODE_PROVIDER:-}" ]]; then + log "OpenCode: provider(s) configured: ${OPENCODE_PROVIDER}" + else + warn "OpenCode is enabled but OPENCODE_PROVIDER is not set." + warn "OpenCode may prompt for a provider selection in the workspace." + fi +fi +