# OpenClaw GitHub Copilot GPT-5.4 Quick Deployment Guide Audience: coding agents Goal: complete a one-shot deployment that makes OpenClaw use GitHub Copilot GPT-5.4, including verification and failure handling. ## Scope - OpenClaw is already installed - GitHub Copilot login is available - The current OpenClaw build may not yet register `github-copilot/gpt-5.4` natively This guide fixes three things in one pass: 1. Set the default model to `github-copilot/gpt-5.4` 2. Add the GitHub Copilot provider definition, IDE auth headers, and model entry 3. Force the current main session to use `gpt-5.4` ## Prerequisite If GitHub Copilot is not logged in yet, run: ```bash openclaw models auth login-github-copilot ``` Optional status check: ```bash openclaw models status --json ``` ## One-shot deployment Run the full script below. It first probes whether Copilot really accepts `gpt-5.4`, then patches OpenClaw config, runtime model registry, and the main session override, and finally restarts the gateway. ```bash set -euo pipefail command -v openclaw >/dev/null || { echo 'missing: openclaw'; exit 1; } command -v python3 >/dev/null || { echo 'missing: python3'; exit 1; } command -v node >/dev/null || { echo 'missing: node'; exit 1; } TOKEN_FILE="$HOME/.openclaw/credentials/github-copilot.token.json" [ -f "$TOKEN_FILE" ] || { echo 'missing Copilot token: run openclaw models auth login-github-copilot' exit 1 } export OPENCLAW_COPILOT_BASE_URL="$(node - <<'NODE' const fs = require('fs'); const tokenPath = `${process.env.HOME}/.openclaw/credentials/github-copilot.token.json`; const { token } = JSON.parse(fs.readFileSync(tokenPath, 'utf8')); const match = token.match(/(?:^|;)\s*proxy-ep=([^;\s]+)/i); const candidates = [ match && match[1] ? `https://${String(match[1]).replace(/^proxy\./i, 'api.')}` : null, 'https://api.enterprise.githubcopilot.com', 'https://api.individual.githubcopilot.com', ].filter(Boolean); const uniqueCandidates = [...new Set(candidates)]; const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, 'User-Agent': 'GitHubCopilotChat/0.35.0', 'Editor-Version': 'vscode/1.107.0', 'Editor-Plugin-Version': 'copilot-chat/0.35.0', 'Copilot-Integration-Id': 'vscode-chat', 'Openai-Intent': 'conversation-edits', 'X-Initiator': 'user', }; (async () => { for (const base of uniqueCandidates) { try { const res = await fetch(`${base}/responses`, { method: 'POST', headers, body: JSON.stringify({ model: 'gpt-5.4', input: [{ role: 'user', content: [{ type: 'input_text', text: 'Reply with OK only' }] }], max_output_tokens: 16, stream: false, store: false, }), }); const body = await res.text(); if (res.ok) { console.error(`probe ok: ${base} status=${res.status}`); process.stdout.write(base); return; } console.error(`probe failed: ${base} status=${res.status} body=${body.slice(0, 160)}`); } catch (error) { console.error(`probe error: ${base} ${String(error)}`); } } process.exit(1); })(); NODE )" echo "using baseUrl=$OPENCLAW_COPILOT_BASE_URL" python3 - <<'PY' import json import os import pathlib home = pathlib.Path.home() base_url = os.environ['OPENCLAW_COPILOT_BASE_URL'] provider_headers = { 'User-Agent': 'GitHubCopilotChat/0.35.0', 'Editor-Version': 'vscode/1.107.0', 'Editor-Plugin-Version': 'copilot-chat/0.35.0', 'Copilot-Integration-Id': 'vscode-chat', 'Openai-Intent': 'conversation-edits', 'X-Initiator': 'user', } model_def = { 'id': 'gpt-5.4', 'name': 'gpt-5.4', 'api': 'openai-responses', 'reasoning': True, 'input': ['text', 'image'], 'cost': {'input': 0, 'output': 0, 'cacheRead': 0, 'cacheWrite': 0}, 'contextWindow': 400000, 'maxTokens': 128000, } def load_json(path): return json.loads(path.read_text()) if path.exists() else {} def write_json(path, data): path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(data, ensure_ascii=True, indent=2) + '\n') def upsert_model(model_list, model): for index, item in enumerate(model_list): if item.get('id') == model['id']: merged = dict(item) merged.update(model) model_list[index] = merged return model_list.append(model) openclaw_config = home / '.openclaw' / 'openclaw.json' if not openclaw_config.exists(): raise SystemExit(f'missing config: {openclaw_config}') config = load_json(openclaw_config) agents = config.setdefault('agents', {}) defaults = agents.setdefault('defaults', {}) default_model = defaults.setdefault('model', {}) default_model['primary'] = 'github-copilot/gpt-5.4' default_model['fallbacks'] = [] defaults.setdefault('models', {})['github-copilot/gpt-5.4'] = {} providers = config.setdefault('models', {}).setdefault('providers', {}) provider = providers.setdefault('github-copilot', {}) provider['baseUrl'] = base_url provider.setdefault('headers', {}).update(provider_headers) provider_models = provider.setdefault('models', []) upsert_model(provider_models, model_def) write_json(openclaw_config, config) runtime_models = home / '.openclaw' / 'agents' / 'main' / 'agent' / 'models.json' runtime = load_json(runtime_models) or {'providers': {}} runtime_provider = runtime.setdefault('providers', {}).setdefault('github-copilot', {}) runtime_provider['baseUrl'] = base_url runtime_provider.setdefault('headers', {}).update(provider_headers) runtime_provider_models = runtime_provider.setdefault('models', []) upsert_model(runtime_provider_models, model_def) write_json(runtime_models, runtime) sessions_file = home / '.openclaw' / 'agents' / 'main' / 'sessions' / 'sessions.json' if sessions_file.exists(): sessions = load_json(sessions_file) session = sessions.get('agent:main:main') if isinstance(session, dict): session['modelOverride'] = 'gpt-5.4' session['model'] = 'gpt-5.4' session['modelProvider'] = 'github-copilot' session.pop('fallbackNoticeSelectedModel', None) session.pop('fallbackNoticeActiveModel', None) session.pop('fallbackNoticeReason', None) report = session.get('systemPromptReport') if isinstance(report, dict): report['model'] = 'gpt-5.4' report['provider'] = 'github-copilot' write_json(sessions_file, sessions) print('patched openclaw.json, models.json and sessions.json') PY openclaw gateway restart echo 'DEPLOY_OK' ``` ## Validation ### 1. Validate config state ```bash python3 - <<'PY' import json import pathlib home = pathlib.Path.home() config = json.loads((home / '.openclaw' / 'openclaw.json').read_text()) runtime = json.loads((home / '.openclaw' / 'agents' / 'main' / 'agent' / 'models.json').read_text()) session_path = home / '.openclaw' / 'agents' / 'main' / 'sessions' / 'sessions.json' session = {} if session_path.exists(): session = json.loads(session_path.read_text()).get('agent:main:main', {}) provider_models = runtime.get('providers', {}).get('github-copilot', {}).get('models', []) has_gpt54 = any(item.get('id') == 'gpt-5.4' for item in provider_models) print('primary =', config['agents']['defaults']['model']['primary']) print('provider_base =', config['models']['providers']['github-copilot']['baseUrl']) print('runtime_has_gpt54 =', has_gpt54) print('session_modelOverride =', session.get('modelOverride')) print('session_model =', session.get('model')) PY ``` Expected: - `primary = github-copilot/gpt-5.4` - `runtime_has_gpt54 = True` - if a main session exists, `session_modelOverride = gpt-5.4` ### 2. Validate the real Copilot request ```bash node - <<'NODE' const fs = require('fs'); const tokenPath = `${process.env.HOME}/.openclaw/credentials/github-copilot.token.json`; const { token } = JSON.parse(fs.readFileSync(tokenPath, 'utf8')); const match = token.match(/(?:^|;)\s*proxy-ep=([^;\s]+)/i); const base = match && match[1] ? `https://${String(match[1]).replace(/^proxy\./i, 'api.')}` : 'https://api.enterprise.githubcopilot.com'; const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, 'User-Agent': 'GitHubCopilotChat/0.35.0', 'Editor-Version': 'vscode/1.107.0', 'Editor-Plugin-Version': 'copilot-chat/0.35.0', 'Copilot-Integration-Id': 'vscode-chat', 'Openai-Intent': 'conversation-edits', 'X-Initiator': 'user', }; (async () => { const res = await fetch(`${base}/responses`, { method: 'POST', headers, body: JSON.stringify({ model: 'gpt-5.4', input: [{ role: 'user', content: [{ type: 'input_text', text: 'Reply with OK only' }] }], max_output_tokens: 16, stream: false, store: false, }), }); const body = await res.text(); console.log(JSON.stringify({ status: res.status, ok: res.ok, body: body.slice(0, 120) }, null, 2)); })(); NODE ``` Expected: - `status: 200` - `ok: true` - the response body contains `OK` ### 3. Validate the OpenClaw main session If a main session already exists, run: ```bash SESSION_ID="$(python3 - <<'PY' import json import pathlib path = pathlib.Path.home() / '.openclaw' / 'agents' / 'main' / 'sessions' / 'sessions.json' if path.exists(): session = json.loads(path.read_text()).get('agent:main:main', {}) print(session.get('sessionId', '')) PY )" if [ -n "$SESSION_ID" ]; then openclaw agent --session-id "$SESSION_ID" --message "Reply with exactly OK" --json else echo 'no main session yet: open OpenClaw once, then rerun this validation' fi ``` Expected: - the actual output is `OK` ## Failure handling ### `Unknown model: github-copilot/gpt-5.4` Cause: the runtime model registry was not patched. Fix: rerun the full deployment script and confirm `models.json` contains `gpt-5.4`. ### Missing `Editor-Version` or other IDE auth headers Cause: the provider headers were not written. Fix: rerun the full deployment script and confirm both `openclaw.json` and `models.json` contain `github-copilot.headers`. ### TUI still shows the old model Cause: the main session still has the old `modelOverride`, or the client has not reconnected. Fix: 1. Rerun the full deployment script 2. Run `openclaw gateway restart` 3. Fully reconnect the TUI or client ### The initial probe request fails Cause: this is usually not an OpenClaw config issue. It is usually a Copilot login, token, or entitlement issue. Fix: 1. Run `openclaw models auth login-github-copilot` again 2. Rerun the deployment script ## Short execution summary for future agents 1. Log into GitHub Copilot 2. Run the one-shot deployment script 3. Run the validation steps 4. If the TUI still shows the old model, reconnect the client Do not patch only `openclaw.json`. Older OpenClaw builds often also require `models.json` and the current main session to be updated.