[OpenAI OAuthがTTYを要求:非インタラクティブ認証失敗] - OpenAI OAuth Login Requires TTY: Non-Interactive Authentication Failure
`openclaw models auth login`コマンドは、必須のTTY検出により非インタラクティブ環境で失敗するため、コпанニオンアプリやスクリプトがOpenAI Codex OAuthフローの自動化をブロックされます。
🔍 症状
主な症状
非対話型環境(CI/CDパイプライン、companionアプリケーション、リモートシェル)から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]ウィザードは以下のステップを順番に実行します:
- QuickStart — 初期設定の選択
- Use Existing — 既存の設定を選択または新規作成
- Channel Selection — チャンネル設定をスキップ
- Skills Installation — スキルを辞退
- Hooks Configuration — webhook設定をスキップ
- Agent Hatching — hatchingを辞退
- 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ブラウザリダイレクトは自動化されたトークン漏出を防ぐために人間の確認を必要とします。元の実装では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フローの実行パス
認証シーケンスは以下の内部チェーンに従います:
openclaw models auth login --provider openai-codexが呼び出されるrequireInteractive()チェックが実行される- TTYがない場合 → 即座にエラーで終了
- TTYがある場合 →
OAuthFlowManager.start()が呼び出される openai-auth://authorize?...カスタムプロトコルでブラウザが開くlocalhost:8765/callback経由でトークンをポーリングで待機- トークンが
~/.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
} 設定ストレージの保存先
認証に成功したトークンは以下に保存されます:
~/.openclaw/
└── credentials/
└── openai-codex.json # Contains encrypted refresh_token, expires_at🛠️ 解決手順
解決策A:非対話型OAuthコマンド(推奨)
認証コマンドを変更してヘッドレス操作をサポートし、TTY検証をスキップ하지만ブラウザリダイレクトは維持する–no-interactiveフラグを導入します:
# 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-interactivesrc/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:環境変数による上書き
companionアプリケーション向け、全身で制限をバイパスするには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:Companion AppのOAuthトークン注入
OAuthを外部で管理するアプリケーションの場合、トークンを直接credentialsストアに書き込みます:
# 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 timeoutCredentialストレージを確認
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 worksCompanion Appトークン注入が確認できたことを確認
# 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 98762. ブラウザが誤った環境で開く
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-launch3. 古いCredentialファイルの権限
以前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. 長時間操作中のトークン期限切れ
companion appが自動更新を実装していない場合、refreshトークンが期限切れになる可能性があります:
# 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-codex5. Dockerコンテナの隔離
適切な設定を行わないと、Dockerコンテナ内からOAuthブラウザリダイレクトは機能しません:
# 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-app6. KatClaw Companion App固有の問題
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
症状: トークンは存在するが、API呼び出しが401で失敗する。
原因: 取り消されたOAuthトークンまたは期限切れのcredentialsファイル。
参照:src/auth/token-validator.tsECONNREFUSED callback-server
症状: OAuthがブラウザで完了するが、CLIがコールバック失敗を報告する。
原因: localhostをブロックしているファイアウォールまたはリスニングしていないポート。
参照:src/auth/callback-server.tsENOENT credentials file not found
症状:openclaw models auth statusがcredentialsなしを報告する。
原因: credentialsディレクトリが存在しないか、初期化されていない。
参照:src/config/credentials-store.tsENOTTY 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