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.4natively
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:
openclaw models auth login-github-copilot
Optional status check:
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.
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
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.4runtime_has_gpt54 = True- if a main session exists,
session_modelOverride = gpt-5.4
2. Validate the real Copilot request
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: 200ok: true- the response body contains
OK
3. Validate the OpenClaw main session
If a main session already exists, run:
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.