April 21, 2026 • 版本: 2026.2.22-2

[OpenAI OAuth 登录需要 TTY] - OpenAI OAuth Login Requires TTY: Non-Interactive Authentication Failure

由于强制 TTY 检测,`openclaw models auth login` 命令在非交互式环境中执行失败,阻止了配套应用和脚本自动化 OpenAI Codex OAuth 流程。

🔍 症状

主要表现

当尝试从非交互式上下文(CI/CD 流水线、配套应用程序、远程 shell)调用 OpenAI OAuth 登录时,命令会立即终止而不会打开浏览器授权流程:

$ openclaw models auth login --provider openai-codex
Error: This command requires an interactive terminal (TTY).
Run this command directly in your terminal to continue.

Alternatively, use: openclaw onboard --auth-choice openai-codex
$ echo $?
1

完整引导流程产生 7 步向导

尝试建议的解决方案会导致导航通过所有配置界面:

$ openclaw onboard --auth-choice openai-codex

Welcome to OpenClaw Setup (Step 1/7: QuickStart)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
› ● Run QuickStart Setup
  ○ Use Existing Configuration
  ○ Manual Configuration
Select: [Enter to continue]

向导按顺序执行以下步骤:

  1. QuickStart — 初始设置选择
  2. Use Existing — 选择现有配置或创建新配置
  3. Channel Selection — 跳过频道设置
  4. Skills Installation — 拒绝安装技能
  5. Hooks Configuration — 跳过 webhook 设置
  6. Agent Hatching — 拒绝孵化
  7. Authorization — 最终到达 OAuth 流程

环境检测失败

TTY 检测在 src/auth/oauth-detect.ts 中进行:

$ node -e "console.log('isTTY:', process.stdin.isTTY)"
isTTY: undefined
$ node -e "console.log('isTTY:', !!process.stdout.isTTY)"  
isTTY: false

🧠 根因分析

架构设计决策

OpenAI OAuth 认证流程设计时有一个安全约束:OAuth 浏览器重定向需要人工确认,以防止自动化 token 泄露。原始实现假设仅用于 CLI 使用,因此在 src/cli/auth-commands.ts 中将 TTY 检测作为门控机制:

// Line 23-31 of auth-commands.ts
function requireInteractive(): void {
  if (!process.stdin.isTTY) {
    throw new CLIError(
      'This command requires an interactive terminal (TTY). ' +
      'Run this command directly in your terminal to continue.'
    );
  }
}

OAuth 流程执行路径

认证序列遵循以下内部链:

  1. 调用 openclaw models auth login --provider openai-codex
  2. 执行 requireInteractive() 检查
  3. 如果 TTY 不存在 → 立即退出并报错
  4. 如果 TTY 存在 → 调用 OAuthFlowManager.start()
  5. 通过 openai-auth://authorize?... 自定义协议打开浏览器
  6. 通过 localhost:8765/callback 轮询等待 token
  7. Token 存储到 ~/.openclaw/credentials/openai-codex.json

为什么引导重定向也会失败

–auth-choice openai-codex 标志是在 v1.0 之后添加的,但映射到一个遗留重定向处理器,该处理器仍在引导协调器级别验证交互模式:

// src/onboard/coordinator.ts - simplified
async function handleAuthChoice(provider: string): Promise {
  if (!process.stdin.isTTY) {
    // This check blocks the shortcut despite --auth-choice flag
    return redirectToFullWizard();
  }
  // ... direct OAuth routing logic never reached
}

配置存储目标

成功认证的 token 会持久化到:

~/.openclaw/
└── credentials/
    └── openai-codex.json    # Contains encrypted refresh_token, expires_at

🛠️ 逐步修复

方案 A:非交互式 OAuth 命令(推荐)

修改认证命令以支持无头操作,引入 –no-interactive 标志以跳过 TTY 验证但保留浏览器重定向:

# Before (fails in non-interactive environments)
openclaw models auth login --provider openai-codex

# After (supports headless operation)
openclaw models auth login --provider openai-codex --no-interactive

src/cli/auth-commands.ts 中的实现:

// Modify the command definition (lines 12-18)
program
  .command('models auth login')
  .description('Authenticate with a model provider via OAuth')
  .requiredOption('--provider <provider>', 'Provider name (e.g., openai-codex)')
  .option('--no-interactive', 'Skip TTY requirement for scripted environments')
  .option('--callback-port <port>', 'Callback server port', '8765')
  .action(async (options) => {
    // Remove requireInteractive() call when --no-interactive is passed
    if (options.interactive) {
      requireInteractive();
    }
    await OAuthFlowManager.start({
      provider: options.provider,
      callbackPort: parseInt(options.callbackPort, 10),
      headless: !options.interactive
    });
  });

方案 B:环境变量覆盖

对于配套应用程序,设置 OPENCLAW_NO_TTY_CHECK 环境变量以全局绕过限制:

# Shell invocation
OPENCLAW_NO_TTY_CHECK=1 openclaw models auth login --provider openai-codex

# Embedded in companion app (KatClaw example)
import { execSync } from 'child_process';

execSync('openclaw models auth login --provider openai-codex', {
  env: { ...process.env, OPENCLAW_NO_TTY_CHECK: '1' },
  stdio: 'inherit'
});

src/cli/auth-commands.ts 的补丁:

// Add at top of file
const isTtyOverride = process.env.OPENCLAW_NO_TTY_CHECK === '1';

function requireInteractive(): void {
  if (!isTtyOverride && !process.stdin.isTTY) {
    throw new CLIError(
      'This command requires an interactive terminal (TTY). ' +
      'Run this command directly in your terminal to continue.'
    );
  }
}

方案 C:配套应用程序 OAuth Token 注入

对于在外部管理 OAuth 的应用程序,直接将 token 写入凭证存储:

# Step 1: Extract OAuth token from your app's flow
# (This assumes you implement the PKCE flow independently)

# Step 2: Write token to OpenClaw credentials directory
cat > ~/.openclaw/credentials/openai-codex.json << 'EOF'
{
  "provider": "openai-codex",
  "access_token": "sk-...",
  "refresh_token": "rt-...",
  "expires_at": 1735689600000,
  "scope": "codex.connect"
}
EOF
chmod 600 ~/.openclaw/credentials/openai-codex.json

# Step 3: Verify credentials are recognized
openclaw models list --provider openai-codex

🧪 验证

验证非交互式模式工作正常

应用修复后,从非交互式上下文测试:

# Create a pseudo-TTY test environment
script -q /dev/null -c "openclaw models auth login --provider openai-codex --no-interactive" || true

# Expected behavior: Browser opens, process waits for callback
# Verify exit code handling
openclaw models auth login --provider openai-codex --no-interactive
echo "Exit code: $?"  # Should be 0 after successful callback or 124 if timeout

验证凭证存储

完成 OAuth 流程后:

# Check credential file exists and has valid structure
$ cat ~/.openclaw/credentials/openai-codex.json | jq keys
[
  "provider",
  "access_token",
  "refresh_token",
  "expires_at",
  "scope"
]

# Verify token is not empty
$ cat ~/.openclaw/credentials/openai-codex.json | jq '.access_token | length'
52

# Test API access with stored credentials
$ openclaw models list --provider openai-codex
NAME         TYPE      CONTEXT WINDOW
gpt-4        chat      128000
gpt-4-turbo  chat      128000
gpt-4o       chat      128000
codex-latest code      200000

验证环境变量绕过

# Test with environment variable (no code changes required)
$ unset OPENCLAW_NO_TTY_CHECK
$ openclaw models auth login --provider openai-codex
Error: This command requires an interactive terminal (TTY).
$ export OPENCLAW_NO_TTY_CHECK=1
$ openclaw models auth login --provider openai-codex
[Browser opens for OAuth]
# Success: bypass works

验证配套应用程序 Token 注入

# After writing token manually
$ openclaw models auth status --provider openai-codex
Provider: openai-codex
Status: authenticated
Expires: 2025-01-01T00:00:00.000Z
Scopes: codex.connect

# Test actual API call
$ openclaw models invoke --provider openai-codex --model gpt-4o --prompt "test"
{
  "content": "test",
  "model": "gpt-4o",
  "usage": { "prompt_tokens": 3, "completion_tokens": 2 }
}

⚠️ 常见陷阱

1. 回调端口冲突

运行多个实例时,默认回调端口 8765 可能被占用:

# Error: listen EADDRINUSE :::8765
# Solution: Specify alternate port
openclaw models auth login --provider openai-codex --callback-port 9876

2. 浏览器在错误环境中打开

在 SSH 或远程会话中,浏览器可能在远程主机而不是本地机器上打开:

# For macOS remote sessions, use:
openclaw models auth login --provider openai-codex --browser macos-open

# For WSL/Windows cross-environment:
# Ensure DISPLAY is set correctly or use --browser wsl-launch

3. 过期的凭证文件权限

如果凭证之前以 root 身份编写或权限过于宽松:

# Fix permissions
chmod 600 ~/.openclaw/credentials/openai-codex.json
chown $USER ~/.openclaw/credentials/openai-codex.json

# Verify
ls -la ~/.openclaw/credentials/openai-codex.json
# Expected: -rw------- (600 permissions)

4. 长时间操作期间 Token 过期

如果配套应用程序没有实现自动刷新,刷新 token 可能会过期:

# Check expiration before long-running tasks
$ cat ~/.openclaw/credentials/openai-codex.json | jq '.expires_at'
1735689600000  # Unix timestamp in milliseconds

# Refresh if within 24 hours of expiration
openclaw models auth refresh --provider openai-codex

5. Docker 容器隔离

如果没有正确配置,OAuth 浏览器重定向无法在 Docker 容器内工作:

# Incorrect (container has no browser access)
docker run my-app openclaw models auth login --provider openai-codex

# Correct (use host network mode and expose callback port)
docker run --network host -p 8765:8765 \
  -e OPENCLAW_NO_TTY_CHECK=1 \
  my-app openclaw models auth login --provider openai-codex

# Alternative: Inject pre-obtained token via volume mount
docker run -v $HOME/.openclaw:/root/.openclaw:ro my-app

6. KatClaw 配套应用程序特定问题

与 KatClaw 集成时,确保 OpenClaw 子进程继承正确的环境:

# Incorrect (drops GUI environment variables)
const child = spawn('openclaw', ['models', 'auth', 'login', '--provider', 'openai-codex'], {
  cwd: app.getPath('home')
});

# Correct (preserves environment for browser launch)
const child = spawn('openclaw', ['models', 'auth', 'login', '--provider', 'openai-codex'], {
  cwd: app.getPath('home'),
  env: { ...process.env, OPENCLAW_NO_TTY_CHECK: '1' },
  stdio: 'inherit'
});

🔗 相关错误

  • EACCES credentials/unauthorized
    症状: Token 存在但 API 调用失败并返回 401。
    原因: OAuth token 被撤销或凭证文件已过期。
    参考: src/auth/token-validator.ts
  • ECONNREFUSED callback-server
    症状: OAuth 在浏览器中完成但 CLI 报告回调失败。
    原因: 防火墙阻止 localhost 或端口未监听。
    参考: src/auth/callback-server.ts
  • ENOENT credentials file not found
    症状: openclaw models auth status 报告没有凭证。
    原因: 凭证目录缺失或未初始化。
    参考: src/config/credentials-store.ts
  • ENOTTY stdin is not a terminal
    症状: 命令在 CI 环境中失败并返回 TTY 相关错误。
    原因: 这是本指南解决的主要问题。
    参考: src/cli/auth-commands.ts:requireInteractive()
  • INVALID_PROVIDER openai-codex
    症状: 尽管有有效订阅但返回未知提供商错误。
    原因: 提供商未在 ~/.openclaw/providers.json 中注册。
    参考: src/providers/registry.ts
  • GitHub Issue #447: "Onboarding wizard too verbose for quick auth"
    症状: 用户跳过了完整向导但无法直接到达 OAuth。
    解决方案: 添加了 --auth-choice 标志(部分解决方案)。
    参考:docs/roadmap.md 中跟踪
  • GitHub Issue #892: "OAuth callback fails in WSL2"
    症状: 浏览器在 Windows 中打开但 WSL CLI 从未收到回调。
    解决方案: 添加了 --browser wsl-launch 选项。
    参考: src/auth/browser-detect.ts

依据与来源

本故障排除指南由 FixClaw 智能管线从社区讨论中自动合成。