diff --git a/.changeset/patch-update-changeset-generator-to-codex-gpt-5-mini.md b/.changeset/patch-update-changeset-generator-to-codex-gpt-5-mini.md
new file mode 100644
index 00000000000..a6729166046
--- /dev/null
+++ b/.changeset/patch-update-changeset-generator-to-codex-gpt-5-mini.md
@@ -0,0 +1,10 @@
+---
+"gh-aw": patch
+---
+
+Update the changeset generator workflow to use the `codex` engine with the
+`gpt-5-mini` model. Add `strict: false` and remove the `firewall: true` network
+setting to accommodate the codex engine's network behavior.
+
+This is an internal tooling change.
+
diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml
index 1e114a7b582..a434bec2756 100644
--- a/.github/workflows/changeset.lock.yml
+++ b/.github/workflows/changeset.lock.yml
@@ -35,7 +35,10 @@
# contents: read
# pull-requests: read
# issues: read
-# engine: copilot
+# engine:
+# id: codex
+# model: gpt-5-mini
+# strict: false # Required: codex engine doesn't support network firewall
# safe-outputs:
# push-to-pull-request-branch:
# commit-title-suffix: " [skip-ci]"
@@ -48,7 +51,6 @@
# allowed:
# - defaults
# - node
-# firewall: true
# tools:
# bash:
# - "*"
@@ -1284,37 +1286,29 @@ jobs:
main().catch(error => {
core.setFailed(error instanceof Error ? error.message : String(error));
});
- - name: Validate COPILOT_GITHUB_TOKEN or COPILOT_CLI_TOKEN secret
+ - name: Validate CODEX_API_KEY or OPENAI_API_KEY secret
run: |
- if [ -z "$COPILOT_GITHUB_TOKEN" ] && [ -z "$COPILOT_CLI_TOKEN" ]; then
- echo "Error: Neither COPILOT_GITHUB_TOKEN nor COPILOT_CLI_TOKEN secret is set"
- echo "The GitHub Copilot CLI engine requires either COPILOT_GITHUB_TOKEN or COPILOT_CLI_TOKEN secret to be configured."
+ if [ -z "$CODEX_API_KEY" ] && [ -z "$OPENAI_API_KEY" ]; then
+ echo "Error: Neither CODEX_API_KEY nor OPENAI_API_KEY secret is set"
+ echo "The Codex engine requires either CODEX_API_KEY or OPENAI_API_KEY secret to be configured."
echo "Please configure one of these secrets in your repository settings."
- echo "Documentation: https://githubnext.github.io/gh-aw/reference/engines/#github-copilot-default"
+ echo "Documentation: https://githubnext.github.io/gh-aw/reference/engines/#openai-codex"
exit 1
fi
- if [ -n "$COPILOT_GITHUB_TOKEN" ]; then
- echo "COPILOT_GITHUB_TOKEN secret is configured"
+ if [ -n "$CODEX_API_KEY" ]; then
+ echo "CODEX_API_KEY secret is configured"
else
- echo "COPILOT_CLI_TOKEN secret is configured (using as fallback for COPILOT_GITHUB_TOKEN)"
+ echo "OPENAI_API_KEY secret is configured (using as fallback for CODEX_API_KEY)"
fi
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- COPILOT_CLI_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
+ CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }}
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
with:
node-version: '24'
- - name: Install awf binary
- run: |
- echo "Installing awf from release: v0.5.0"
- curl -L https://github.com/githubnext/gh-aw-firewall/releases/download/v0.5.0/awf-linux-x64 -o awf
- chmod +x awf
- sudo mv awf /usr/local/bin/
- which awf
- awf --version
- - name: Install GitHub Copilot CLI
- run: npm install -g @github/copilot@0.0.365
+ - name: Install Codex
+ run: npm install -g @openai/codex@0.63.0
- name: Downloading container images
run: |
set -e
@@ -2178,54 +2172,40 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
run: |
mkdir -p /tmp/gh-aw/mcp-config
- mkdir -p /home/runner/.copilot
- cat > /home/runner/.copilot/mcp-config.json << EOF
- {
- "mcpServers": {
- "github": {
- "type": "local",
- "command": "docker",
- "args": [
- "run",
- "-i",
- "--rm",
- "-e",
- "GITHUB_PERSONAL_ACCESS_TOKEN",
- "-e",
- "GITHUB_READ_ONLY=1",
- "-e",
- "GITHUB_TOOLSETS=default",
- "ghcr.io/github/github-mcp-server:v0.23.0"
- ],
- "tools": ["*"],
- "env": {
- "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}"
- }
- },
- "safeoutputs": {
- "type": "local",
- "command": "node",
- "args": ["/tmp/gh-aw/safeoutputs/mcp-server.cjs"],
- "tools": ["*"],
- "env": {
- "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}",
- "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}",
- "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}",
- "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}",
- "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
- "GITHUB_SERVER_URL": "\${GITHUB_SERVER_URL}"
- }
- }
- }
- }
+ cat > /tmp/gh-aw/mcp-config/config.toml << EOF
+ [history]
+ persistence = "none"
+
+ [shell_environment_policy]
+ inherit = "core"
+ include_only = ["CODEX_API_KEY", "GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_SAFE_OUTPUTS", "GITHUB_PERSONAL_ACCESS_TOKEN", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "HOME", "OPENAI_API_KEY", "PATH"]
+
+ [mcp_servers.github]
+ user_agent = "changeset-generator"
+ startup_timeout_sec = 120
+ tool_timeout_sec = 60
+ command = "docker"
+ args = [
+ "run",
+ "-i",
+ "--rm",
+ "-e",
+ "GITHUB_PERSONAL_ACCESS_TOKEN",
+ "-e",
+ "GITHUB_READ_ONLY=1",
+ "-e",
+ "GITHUB_TOOLSETS=default",
+ "ghcr.io/github/github-mcp-server:v0.23.0"
+ ]
+ env_vars = ["GITHUB_PERSONAL_ACCESS_TOKEN"]
+
+ [mcp_servers.safeoutputs]
+ command = "node"
+ args = [
+ "/tmp/gh-aw/safeoutputs/mcp-server.cjs",
+ ]
+ env_vars = ["GH_AW_SAFE_OUTPUTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_ASSETS_ALLOWED_EXTS", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL"]
EOF
- echo "-------START MCP CONFIG-----------"
- cat /home/runner/.copilot/mcp-config.json
- echo "-------END MCP CONFIG-----------"
- echo "-------/home/runner/.copilot-----------"
- find /home/runner/.copilot
- echo "HOME: $HOME"
- echo "GITHUB_COPILOT_CLI_MODE: $GITHUB_COPILOT_CLI_MODE"
- name: Generate agentic run info
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
@@ -2233,13 +2213,13 @@ jobs:
const fs = require('fs');
const awInfo = {
- engine_id: "copilot",
- engine_name: "GitHub Copilot CLI",
- model: "",
+ engine_id: "codex",
+ engine_name: "Codex",
+ model: "gpt-5-mini",
version: "",
- agent_version: "0.0.365",
+ agent_version: "0.63.0",
workflow_name: "Changeset Generator",
- experimental: false,
+ experimental: true,
supports_tools_allowlist: true,
supports_http_transport: true,
run_id: context.runId,
@@ -2253,10 +2233,10 @@ jobs:
staged: false,
network_mode: "defaults",
allowed_domains: ["defaults","node"],
- firewall_enabled: true,
+ firewall_enabled: false,
firewall_version: "",
steps: {
- firewall: "squid"
+ firewall: ""
},
created_at: new Date().toISOString()
};
@@ -2733,37 +2713,23 @@ jobs:
name: aw_info.json
path: /tmp/gh-aw/aw_info.json
if-no-files-found: warn
- - name: Execute GitHub Copilot CLI
- id: agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
+ - name: Run Codex
run: |
set -o pipefail
- sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --mount /tmp:/tmp:rw --mount "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}:rw" --mount "${GITHUB_WORKSPACE}/.github:/workspace/.github:rw" --allow-domains api.enterprise.githubcopilot.com,api.github.com,api.npms.io,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,bun.sh,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deno.land,get.pnpm.io,github.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,skimdb.npmjs.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.npmjs.com,www.npmjs.org,yarnpkg.com --log-level info \
- -- npx -y @github/copilot@0.0.365 --add-dir /tmp/gh-aw/ --log-level all --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" \
- 2>&1 | tee /tmp/gh-aw/agent-stdio.log
-
- # Move preserved agent logs to expected location
- # Try new naming convention first (awf-agent-logs-*), fall back to legacy (copilot-logs-*) for backward compatibility
- AGENT_LOGS_DIR="$(find /tmp -maxdepth 1 -type d \( -name 'awf-agent-logs-*' -o -name 'copilot-logs-*' \) -print0 2>/dev/null | xargs -0 ls -td 2>/dev/null | head -1)"
- if [ -n "$AGENT_LOGS_DIR" ] && [ -d "$AGENT_LOGS_DIR" ]; then
- echo "Moving agent logs from $AGENT_LOGS_DIR to /tmp/gh-aw/.agent/logs/"
- sudo mkdir -p /tmp/gh-aw/.agent/logs/
- sudo mv "$AGENT_LOGS_DIR"/* /tmp/gh-aw/.agent/logs/ || true
- sudo rmdir "$AGENT_LOGS_DIR" || true
- fi
+ INSTRUCTION="$(cat "$GH_AW_PROMPT")"
+ mkdir -p "$CODEX_HOME/logs"
+ codex -c model=gpt-5-mini exec --full-auto --skip-git-repo-check "$INSTRUCTION" 2>&1 | tee /tmp/gh-aw/agent-stdio.log
env:
- COPILOT_AGENT_RUNNER_TYPE: STANDALONE
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN || secrets.COPILOT_CLI_TOKEN }}
- GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
+ CODEX_API_KEY: ${{ secrets.CODEX_API_KEY || secrets.OPENAI_API_KEY }}
+ CODEX_HOME: /tmp/gh-aw/mcp-config
+ GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ GH_AW_MCP_CONFIG: /tmp/gh-aw/mcp-config/config.toml
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- GITHUB_HEAD_REF: ${{ github.head_ref }}
- GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
- GITHUB_WORKSPACE: ${{ github.workspace }}
- XDG_CONFIG_HOME: /home/runner
+ OPENAI_API_KEY: ${{ secrets.CODEX_API_KEY || secrets.OPENAI_API_KEY }}
+ RUST_LOG: trace,hyper_util=info,mio=info,reqwest=info,os_info=info,codex_otel=warn,codex_core=debug,ocodex_exec=debug
- name: Redact secrets in logs
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
@@ -2875,11 +2841,11 @@ jobs:
}
await main();
env:
- GH_AW_SECRET_NAMES: 'COPILOT_CLI_TOKEN,COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
- SECRET_COPILOT_CLI_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
- SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_SECRET_NAMES: 'CODEX_API_KEY,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN,OPENAI_API_KEY'
+ SECRET_CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }}
SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SECRET_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Upload Safe Outputs
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
@@ -2892,7 +2858,7 @@ jobs:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- GH_AW_ALLOWED_DOMAINS: "api.enterprise.githubcopilot.com,api.github.com,api.npms.io,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,bun.sh,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deno.land,get.pnpm.io,github.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,skimdb.npmjs.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.npmjs.com,www.npmjs.org,yarnpkg.com"
+ GH_AW_ALLOWED_DOMAINS: "crl3.digicert.com,crl4.digicert.com,ocsp.digicert.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,crl.geotrust.com,ocsp.geotrust.com,crl.thawte.com,ocsp.thawte.com,crl.verisign.com,ocsp.verisign.com,crl.globalsign.com,ocsp.globalsign.com,crls.ssl.com,ocsp.ssl.com,crl.identrust.com,ocsp.identrust.com,crl.sectigo.com,ocsp.sectigo.com,crl.usertrust.com,ocsp.usertrust.com,s.symcb.com,s.symcd.com,json-schema.org,json.schemastore.org,archive.ubuntu.com,security.ubuntu.com,ppa.launchpad.net,keyserver.ubuntu.com,azure.archive.ubuntu.com,api.snapcraft.io,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,npmjs.org,npmjs.com,www.npmjs.com,www.npmjs.org,registry.npmjs.com,registry.npmjs.org,skimdb.npmjs.com,npm.pkg.github.com,api.npms.io,nodejs.org,yarnpkg.com,registry.yarnpkg.com,repo.yarnpkg.com,deb.nodesource.com,get.pnpm.io,bun.sh,deno.land,registry.bower.io"
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_API_URL: ${{ github.api_url }}
with:
@@ -4094,7 +4060,7 @@ jobs:
with:
name: agent_outputs
path: |
- /tmp/gh-aw/.agent/logs/
+ /tmp/gh-aw/mcp-config/logs/
/tmp/gh-aw/redacted-urls.log
if-no-files-found: ignore
- name: Upload MCP logs
@@ -4108,7 +4074,7 @@ jobs:
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
- GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
+ GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
with:
script: |
function runLogParser(options) {
@@ -4643,832 +4609,316 @@ jobs:
}
function main() {
runLogParser({
- parseLog: parseCopilotLog,
- parserName: "Copilot",
- supportsDirectories: true,
+ parseLog: parseCodexLog,
+ parserName: "Codex",
+ supportsDirectories: false,
});
}
- function extractPremiumRequestCount(logContent) {
- const patterns = [
- /premium\s+requests?\s+consumed:?\s*(\d+)/i,
- /(\d+)\s+premium\s+requests?\s+consumed/i,
- /consumed\s+(\d+)\s+premium\s+requests?/i,
- ];
- for (const pattern of patterns) {
- const match = logContent.match(pattern);
- if (match && match[1]) {
- const count = parseInt(match[1], 10);
- if (!isNaN(count) && count > 0) {
- return count;
+ function extractMCPInitialization(lines) {
+ const mcpServers = new Map();
+ let serverCount = 0;
+ let connectedCount = 0;
+ let availableTools = [];
+ for (const line of lines) {
+ if (line.includes("Initializing MCP servers") || (line.includes("mcp") && line.includes("init"))) {
+ }
+ const countMatch = line.match(/Found (\d+) MCP servers? in configuration/i);
+ if (countMatch) {
+ serverCount = parseInt(countMatch[1]);
+ }
+ const connectingMatch = line.match(/Connecting to MCP server[:\s]+['"]?(\w+)['"]?/i);
+ if (connectingMatch) {
+ const serverName = connectingMatch[1];
+ if (!mcpServers.has(serverName)) {
+ mcpServers.set(serverName, { name: serverName, status: "connecting" });
}
}
- }
- return 1;
- }
- function parseCopilotLog(logContent) {
- try {
- let logEntries;
- try {
- logEntries = JSON.parse(logContent);
- if (!Array.isArray(logEntries)) {
- throw new Error("Not a JSON array");
- }
- } catch (jsonArrayError) {
- const debugLogEntries = parseDebugLogFormat(logContent);
- if (debugLogEntries && debugLogEntries.length > 0) {
- logEntries = debugLogEntries;
- } else {
- logEntries = parseLogEntries(logContent);
+ const connectedMatch = line.match(/MCP server ['"](\w+)['"] connected successfully/i);
+ if (connectedMatch) {
+ const serverName = connectedMatch[1];
+ mcpServers.set(serverName, { name: serverName, status: "connected" });
+ connectedCount++;
+ }
+ const failedMatch = line.match(/Failed to connect to MCP server ['"](\w+)['"][:]\s*(.+)/i);
+ if (failedMatch) {
+ const serverName = failedMatch[1];
+ const error = failedMatch[2].trim();
+ mcpServers.set(serverName, { name: serverName, status: "failed", error });
+ }
+ const initFailedMatch = line.match(/MCP server ['"](\w+)['"] initialization failed/i);
+ if (initFailedMatch) {
+ const serverName = initFailedMatch[1];
+ const existing = mcpServers.get(serverName);
+ if (existing && existing.status !== "failed") {
+ mcpServers.set(serverName, { name: serverName, status: "failed", error: "Initialization failed" });
}
}
- if (!logEntries) {
- return "## Agent Log Summary\n\nLog format not recognized as Copilot JSON array or JSONL.\n";
- }
- const conversationResult = generateConversationMarkdown(logEntries, {
- formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: true }),
- formatInitCallback: initEntry =>
- formatInitializationSummary(initEntry, {
- includeSlashCommands: false,
- modelInfoCallback: entry => {
- if (!entry.model_info) return "";
- const modelInfo = entry.model_info;
- let markdown = "";
- if (modelInfo.name) {
- markdown += `**Model Name:** ${modelInfo.name}`;
- if (modelInfo.vendor) {
- markdown += ` (${modelInfo.vendor})`;
- }
- markdown += "\n\n";
- }
- if (modelInfo.billing) {
- const billing = modelInfo.billing;
- if (billing.is_premium === true) {
- markdown += `**Premium Model:** Yes`;
- if (billing.multiplier && billing.multiplier !== 1) {
- markdown += ` (${billing.multiplier}x cost multiplier)`;
- }
- markdown += "\n";
- if (billing.restricted_to && Array.isArray(billing.restricted_to) && billing.restricted_to.length > 0) {
- markdown += `**Required Plans:** ${billing.restricted_to.join(", ")}\n`;
- }
- markdown += "\n";
- } else if (billing.is_premium === false) {
- markdown += `**Premium Model:** No\n\n`;
- }
- }
- return markdown;
- },
- }),
- });
- let markdown = conversationResult.markdown;
- const lastEntry = logEntries[logEntries.length - 1];
- const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init");
- markdown += generateInformationSection(lastEntry, {
- additionalInfoCallback: entry => {
- const isPremiumModel =
- initEntry && initEntry.model_info && initEntry.model_info.billing && initEntry.model_info.billing.is_premium === true;
- if (isPremiumModel) {
- const premiumRequestCount = extractPremiumRequestCount(logContent);
- return `**Premium Requests Consumed:** ${premiumRequestCount}\n\n`;
- }
- return "";
- },
- });
- return markdown;
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- return `## Agent Log Summary\n\nError parsing Copilot log (tried both JSON array and JSONL formats): ${errorMessage}\n`;
+ const toolsMatch = line.match(/Available tools:\s*(.+)/i);
+ if (toolsMatch) {
+ const toolsStr = toolsMatch[1];
+ availableTools = toolsStr
+ .split(",")
+ .map(t => t.trim())
+ .filter(t => t.length > 0);
+ }
}
- }
- function scanForToolErrors(logContent) {
- const toolErrors = new Map();
- const lines = logContent.split("\n");
- const recentToolCalls = [];
- const MAX_RECENT_TOOLS = 10;
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i];
- if (line.includes('"tool_calls":') && !line.includes('\\"tool_calls\\"')) {
- for (let j = i + 1; j < Math.min(i + 30, lines.length); j++) {
- const nextLine = lines[j];
- const idMatch = nextLine.match(/"id":\s*"([^"]+)"/);
- const nameMatch = nextLine.match(/"name":\s*"([^"]+)"/) && !nextLine.includes('\\"name\\"');
- if (idMatch) {
- const toolId = idMatch[1];
- for (let k = j; k < Math.min(j + 10, lines.length); k++) {
- const nameLine = lines[k];
- const funcNameMatch = nameLine.match(/"name":\s*"([^"]+)"/);
- if (funcNameMatch && !nameLine.includes('\\"name\\"')) {
- const toolName = funcNameMatch[1];
- recentToolCalls.unshift({ id: toolId, name: toolName });
- if (recentToolCalls.length > MAX_RECENT_TOOLS) {
- recentToolCalls.pop();
- }
- break;
- }
- }
- }
- }
+ let markdown = "";
+ const hasInfo = mcpServers.size > 0 || availableTools.length > 0;
+ if (mcpServers.size > 0) {
+ markdown += "**MCP Servers:**\n";
+ const servers = Array.from(mcpServers.values());
+ const connected = servers.filter(s => s.status === "connected");
+ const failed = servers.filter(s => s.status === "failed");
+ markdown += `- Total: ${servers.length}${serverCount > 0 && servers.length !== serverCount ? ` (configured: ${serverCount})` : ""}\n`;
+ markdown += `- Connected: ${connected.length}\n`;
+ if (failed.length > 0) {
+ markdown += `- Failed: ${failed.length}\n`;
}
- const errorMatch = line.match(/\[ERROR\].*(?:Tool execution failed|Permission denied|Resource not accessible|Error executing tool)/i);
- if (errorMatch) {
- const toolNameMatch = line.match(/Tool execution failed:\s*([^\s]+)/i);
- const toolIdMatch = line.match(/tool_call_id:\s*([^\s]+)/i);
- if (toolNameMatch) {
- const toolName = toolNameMatch[1];
- toolErrors.set(toolName, true);
- const matchingTool = recentToolCalls.find(t => t.name === toolName);
- if (matchingTool) {
- toolErrors.set(matchingTool.id, true);
- }
- } else if (toolIdMatch) {
- toolErrors.set(toolIdMatch[1], true);
- } else if (recentToolCalls.length > 0) {
- const lastTool = recentToolCalls[0];
- toolErrors.set(lastTool.id, true);
- toolErrors.set(lastTool.name, true);
+ markdown += "\n";
+ for (const server of servers) {
+ const statusIcon = server.status === "connected" ? "✅" : server.status === "failed" ? "❌" : "⏳";
+ markdown += `- ${statusIcon} **${server.name}** (${server.status})`;
+ if (server.error) {
+ markdown += `\n - Error: ${server.error}`;
}
+ markdown += "\n";
}
+ markdown += "\n";
+ }
+ if (availableTools.length > 0) {
+ markdown += "**Available MCP Tools:**\n";
+ markdown += `- Total: ${availableTools.length} tools\n`;
+ markdown += `- Tools: ${availableTools.slice(0, 10).join(", ")}${availableTools.length > 10 ? ", ..." : ""}\n\n`;
}
- return toolErrors;
+ return {
+ hasInfo,
+ markdown,
+ servers: Array.from(mcpServers.values()),
+ };
}
- function parseDebugLogFormat(logContent) {
- const entries = [];
- const lines = logContent.split("\n");
- const toolErrors = scanForToolErrors(logContent);
- let model = "unknown";
- let sessionId = null;
- let modelInfo = null;
- let tools = [];
- const modelMatch = logContent.match(/Starting Copilot CLI: ([\d.]+)/);
- if (modelMatch) {
- sessionId = `copilot-${modelMatch[1]}-${Date.now()}`;
- }
- const gotModelInfoIndex = logContent.indexOf("[DEBUG] Got model info: {");
- if (gotModelInfoIndex !== -1) {
- const jsonStart = logContent.indexOf("{", gotModelInfoIndex);
- if (jsonStart !== -1) {
- let braceCount = 0;
- let inString = false;
- let escapeNext = false;
- let jsonEnd = -1;
- for (let i = jsonStart; i < logContent.length; i++) {
- const char = logContent[i];
- if (escapeNext) {
- escapeNext = false;
- continue;
- }
- if (char === "\\") {
- escapeNext = true;
- continue;
- }
- if (char === '"' && !escapeNext) {
- inString = !inString;
- continue;
- }
- if (inString) continue;
- if (char === "{") {
- braceCount++;
- } else if (char === "}") {
- braceCount--;
- if (braceCount === 0) {
- jsonEnd = i + 1;
- break;
- }
- }
+ function parseCodexLog(logContent) {
+ try {
+ const lines = logContent.split("\n");
+ const LOOKAHEAD_WINDOW = 50;
+ let markdown = "";
+ const mcpInfo = extractMCPInitialization(lines);
+ if (mcpInfo.hasInfo) {
+ markdown += "## 🚀 Initialization\n\n";
+ markdown += mcpInfo.markdown;
+ }
+ markdown += "## 🤖 Reasoning\n\n";
+ let inThinkingSection = false;
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ if (
+ line.includes("OpenAI Codex") ||
+ line.startsWith("--------") ||
+ line.includes("workdir:") ||
+ line.includes("model:") ||
+ line.includes("provider:") ||
+ line.includes("approval:") ||
+ line.includes("sandbox:") ||
+ line.includes("reasoning effort:") ||
+ line.includes("reasoning summaries:") ||
+ line.includes("tokens used:") ||
+ line.includes("DEBUG codex") ||
+ line.includes("INFO codex") ||
+ line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s+(DEBUG|INFO|WARN|ERROR)/)
+ ) {
+ continue;
}
- if (jsonEnd !== -1) {
- const modelInfoJson = logContent.substring(jsonStart, jsonEnd);
- try {
- modelInfo = JSON.parse(modelInfoJson);
- } catch (e) {
- }
+ if (line.trim() === "thinking") {
+ inThinkingSection = true;
+ continue;
}
- }
- }
- const toolsIndex = logContent.indexOf("[DEBUG] Tools:");
- if (toolsIndex !== -1) {
- const afterToolsLine = logContent.indexOf("\n", toolsIndex);
- let toolsStart = logContent.indexOf("[DEBUG] [", afterToolsLine);
- if (toolsStart !== -1) {
- toolsStart = logContent.indexOf("[", toolsStart + 7);
- }
- if (toolsStart !== -1) {
- let bracketCount = 0;
- let inString = false;
- let escapeNext = false;
- let toolsEnd = -1;
- for (let i = toolsStart; i < logContent.length; i++) {
- const char = logContent[i];
- if (escapeNext) {
- escapeNext = false;
- continue;
- }
- if (char === "\\") {
- escapeNext = true;
- continue;
- }
- if (char === '"' && !escapeNext) {
- inString = !inString;
- continue;
- }
- if (inString) continue;
- if (char === "[") {
- bracketCount++;
- } else if (char === "]") {
- bracketCount--;
- if (bracketCount === 0) {
- toolsEnd = i + 1;
+ const toolMatch = line.match(/^tool\s+(\w+)\.(\w+)\(/);
+ if (toolMatch) {
+ inThinkingSection = false;
+ const server = toolMatch[1];
+ const toolName = toolMatch[2];
+ let statusIcon = "❓";
+ for (let j = i + 1; j < Math.min(i + LOOKAHEAD_WINDOW, lines.length); j++) {
+ const nextLine = lines[j];
+ if (nextLine.includes(`${server}.${toolName}(`) && nextLine.includes("success in")) {
+ statusIcon = "✅";
+ break;
+ } else if (nextLine.includes(`${server}.${toolName}(`) && (nextLine.includes("failed in") || nextLine.includes("error"))) {
+ statusIcon = "❌";
break;
}
}
+ markdown += `${statusIcon} ${server}::${toolName}(...)\n\n`;
+ continue;
}
- if (toolsEnd !== -1) {
- let toolsJson = logContent.substring(toolsStart, toolsEnd);
- toolsJson = toolsJson.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /gm, "");
- try {
- const toolsArray = JSON.parse(toolsJson);
- if (Array.isArray(toolsArray)) {
- tools = toolsArray
- .map(tool => {
- if (tool.type === "function" && tool.function && tool.function.name) {
- let name = tool.function.name;
- if (name.startsWith("github-")) {
- name = "mcp__github__" + name.substring(7);
- } else if (name.startsWith("safe_outputs-")) {
- name = name;
- }
- return name;
- }
- return null;
- })
- .filter(name => name !== null);
- }
- } catch (e) {
- }
+ if (inThinkingSection && line.trim().length > 20 && !line.match(/^\d{4}-\d{2}-\d{2}T/)) {
+ const trimmed = line.trim();
+ markdown += `${trimmed}\n\n`;
}
}
- }
- let inDataBlock = false;
- let currentJsonLines = [];
- let turnCount = 0;
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i];
- if (line.includes("[DEBUG] data:")) {
- inDataBlock = true;
- currentJsonLines = [];
- continue;
- }
- if (inDataBlock) {
- const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /);
- if (hasTimestamp) {
- const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, "");
- const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"');
- if (!isJsonContent) {
- if (currentJsonLines.length > 0) {
- try {
- const jsonStr = currentJsonLines.join("\n");
- const jsonData = JSON.parse(jsonStr);
- if (jsonData.model) {
- model = jsonData.model;
+ markdown += "## 🤖 Commands and Tools\n\n";
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ const toolMatch = line.match(/^\[.*?\]\s+tool\s+(\w+)\.(\w+)\((.+)\)/) || line.match(/ToolCall:\s+(\w+)__(\w+)\s+(\{.+\})/);
+ const bashMatch = line.match(/^\[.*?\]\s+exec\s+bash\s+-lc\s+'([^']+)'/);
+ if (toolMatch) {
+ const server = toolMatch[1];
+ const toolName = toolMatch[2];
+ const params = toolMatch[3];
+ let statusIcon = "❓";
+ let response = "";
+ let isError = false;
+ for (let j = i + 1; j < Math.min(i + LOOKAHEAD_WINDOW, lines.length); j++) {
+ const nextLine = lines[j];
+ if (nextLine.includes(`${server}.${toolName}(`) && (nextLine.includes("success in") || nextLine.includes("failed in"))) {
+ isError = nextLine.includes("failed in");
+ statusIcon = isError ? "❌" : "✅";
+ let jsonLines = [];
+ let braceCount = 0;
+ let inJson = false;
+ for (let k = j + 1; k < Math.min(j + 30, lines.length); k++) {
+ const respLine = lines[k];
+ if (respLine.includes("tool ") || respLine.includes("ToolCall:") || respLine.includes("tokens used")) {
+ break;
}
- if (jsonData.choices && Array.isArray(jsonData.choices)) {
- for (const choice of jsonData.choices) {
- if (choice.message) {
- const message = choice.message;
- const content = [];
- const toolResults = [];
- if (message.content && message.content.trim()) {
- content.push({
- type: "text",
- text: message.content,
- });
- }
- if (message.tool_calls && Array.isArray(message.tool_calls)) {
- for (const toolCall of message.tool_calls) {
- if (toolCall.function) {
- let toolName = toolCall.function.name;
- const originalToolName = toolName;
- const toolId = toolCall.id || `tool_${Date.now()}_${Math.random()}`;
- let args = {};
- if (toolName.startsWith("github-")) {
- toolName = "mcp__github__" + toolName.substring(7);
- } else if (toolName === "bash") {
- toolName = "Bash";
- }
- try {
- args = JSON.parse(toolCall.function.arguments);
- } catch (e) {
- args = {};
- }
- content.push({
- type: "tool_use",
- id: toolId,
- name: toolName,
- input: args,
- });
- const hasError = toolErrors.has(toolId) || toolErrors.has(originalToolName);
- toolResults.push({
- type: "tool_result",
- tool_use_id: toolId,
- content: hasError ? "Permission denied or tool execution failed" : "",
- is_error: hasError,
- });
- }
- }
- }
- if (content.length > 0) {
- entries.push({
- type: "assistant",
- message: { content },
- });
- turnCount++;
- if (toolResults.length > 0) {
- entries.push({
- type: "user",
- message: { content: toolResults },
- });
- }
- }
- }
- }
- if (jsonData.usage) {
- if (!entries._accumulatedUsage) {
- entries._accumulatedUsage = {
- input_tokens: 0,
- output_tokens: 0,
- };
- }
- if (jsonData.usage.prompt_tokens) {
- entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens;
- }
- if (jsonData.usage.completion_tokens) {
- entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens;
- }
- entries._lastResult = {
- type: "result",
- num_turns: turnCount,
- usage: entries._accumulatedUsage,
- };
+ for (const char of respLine) {
+ if (char === "{") {
+ braceCount++;
+ inJson = true;
+ } else if (char === "}") {
+ braceCount--;
}
}
- } catch (e) {
- }
- }
- inDataBlock = false;
- currentJsonLines = [];
- continue;
- } else if (hasTimestamp && isJsonContent) {
- currentJsonLines.push(cleanLine);
- }
- } else {
- const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, "");
- currentJsonLines.push(cleanLine);
- }
- }
- }
- if (inDataBlock && currentJsonLines.length > 0) {
- try {
- const jsonStr = currentJsonLines.join("\n");
- const jsonData = JSON.parse(jsonStr);
- if (jsonData.model) {
- model = jsonData.model;
- }
- if (jsonData.choices && Array.isArray(jsonData.choices)) {
- for (const choice of jsonData.choices) {
- if (choice.message) {
- const message = choice.message;
- const content = [];
- const toolResults = [];
- if (message.content && message.content.trim()) {
- content.push({
- type: "text",
- text: message.content,
- });
- }
- if (message.tool_calls && Array.isArray(message.tool_calls)) {
- for (const toolCall of message.tool_calls) {
- if (toolCall.function) {
- let toolName = toolCall.function.name;
- const originalToolName = toolName;
- const toolId = toolCall.id || `tool_${Date.now()}_${Math.random()}`;
- let args = {};
- if (toolName.startsWith("github-")) {
- toolName = "mcp__github__" + toolName.substring(7);
- } else if (toolName === "bash") {
- toolName = "Bash";
- }
- try {
- args = JSON.parse(toolCall.function.arguments);
- } catch (e) {
- args = {};
- }
- content.push({
- type: "tool_use",
- id: toolId,
- name: toolName,
- input: args,
- });
- const hasError = toolErrors.has(toolId) || toolErrors.has(originalToolName);
- toolResults.push({
- type: "tool_result",
- tool_use_id: toolId,
- content: hasError ? "Permission denied or tool execution failed" : "",
- is_error: hasError,
- });
- }
+ if (inJson) {
+ jsonLines.push(respLine);
}
- }
- if (content.length > 0) {
- entries.push({
- type: "assistant",
- message: { content },
- });
- turnCount++;
- if (toolResults.length > 0) {
- entries.push({
- type: "user",
- message: { content: toolResults },
- });
+ if (inJson && braceCount === 0) {
+ break;
}
}
+ response = jsonLines.join("\n");
+ break;
}
}
- if (jsonData.usage) {
- if (!entries._accumulatedUsage) {
- entries._accumulatedUsage = {
- input_tokens: 0,
- output_tokens: 0,
- };
- }
- if (jsonData.usage.prompt_tokens) {
- entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens;
- }
- if (jsonData.usage.completion_tokens) {
- entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens;
+ markdown += formatCodexToolCall(server, toolName, params, response, statusIcon);
+ } else if (bashMatch) {
+ const command = bashMatch[1];
+ let statusIcon = "❓";
+ let response = "";
+ let isError = false;
+ for (let j = i + 1; j < Math.min(i + LOOKAHEAD_WINDOW, lines.length); j++) {
+ const nextLine = lines[j];
+ if (nextLine.includes("bash -lc") && (nextLine.includes("succeeded in") || nextLine.includes("failed in"))) {
+ isError = nextLine.includes("failed in");
+ statusIcon = isError ? "❌" : "✅";
+ let responseLines = [];
+ for (let k = j + 1; k < Math.min(j + 20, lines.length); k++) {
+ const respLine = lines[k];
+ if (
+ respLine.includes("tool ") ||
+ respLine.includes("exec ") ||
+ respLine.includes("ToolCall:") ||
+ respLine.includes("tokens used") ||
+ respLine.includes("thinking")
+ ) {
+ break;
+ }
+ responseLines.push(respLine);
+ }
+ response = responseLines.join("\n").trim();
+ break;
}
- entries._lastResult = {
- type: "result",
- num_turns: turnCount,
- usage: entries._accumulatedUsage,
- };
}
+ markdown += formatCodexBashCall(command, response, statusIcon);
}
- } catch (e) {
}
- }
- if (entries.length > 0) {
- const initEntry = {
- type: "system",
- subtype: "init",
- session_id: sessionId,
- model: model,
- tools: tools,
- };
- if (modelInfo) {
- initEntry.model_info = modelInfo;
+ markdown += "\n## 📊 Information\n\n";
+ let totalTokens = 0;
+ const tokenCountMatches = logContent.matchAll(/total_tokens:\s*(\d+)/g);
+ for (const match of tokenCountMatches) {
+ const tokens = parseInt(match[1]);
+ totalTokens = Math.max(totalTokens, tokens);
}
- entries.unshift(initEntry);
- if (entries._lastResult) {
- entries.push(entries._lastResult);
- delete entries._lastResult;
+ const finalTokensMatch = logContent.match(/tokens used\n([\d,]+)/);
+ if (finalTokensMatch) {
+ totalTokens = parseInt(finalTokensMatch[1].replace(/,/g, ""));
}
- }
- return entries;
- }
- if (typeof module !== "undefined" && module.exports) {
- module.exports = {
- parseCopilotLog,
- extractPremiumRequestCount,
- };
- }
- main();
- - name: Agent Firewall logs
- if: always()
- run: |
- # Squid logs are preserved in timestamped directories
- SQUID_LOGS_DIR="$(find /tmp -maxdepth 1 -type d -name 'squid-logs-*' -print0 2>/dev/null | xargs -0 ls -td 2>/dev/null | head -1)"
- if [ -n "$SQUID_LOGS_DIR" ] && [ -d "$SQUID_LOGS_DIR" ]; then
- echo "Found Squid logs at: $SQUID_LOGS_DIR"
- mkdir -p /tmp/gh-aw/squid-logs-changeset-generator/
- sudo cp -r "$SQUID_LOGS_DIR"/* /tmp/gh-aw/squid-logs-changeset-generator/ || true
- sudo chmod -R a+r /tmp/gh-aw/squid-logs-changeset-generator/ || true
- fi
- - name: Upload Firewall Logs
- if: always()
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
- with:
- name: squid-logs-changeset-generator
- path: /tmp/gh-aw/squid-logs-changeset-generator/
- if-no-files-found: ignore
- - name: Parse firewall logs for step summary
- if: always()
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- with:
- script: |
- function sanitizeWorkflowName(name) {
-
- return name
-
- .toLowerCase()
-
- .replace(/[:\\/\s]/g, "-")
-
- .replace(/[^a-z0-9._-]/g, "-");
-
- }
-
- function main() {
-
- const fs = require("fs");
-
- const path = require("path");
-
- try {
-
- const workflowName = process.env.GITHUB_WORKFLOW || "workflow";
-
- const sanitizedName = sanitizeWorkflowName(workflowName);
-
- const squidLogsDir = `/tmp/gh-aw/squid-logs-${sanitizedName}/`;
-
- if (!fs.existsSync(squidLogsDir)) {
-
- core.info(`No firewall logs directory found at: ${squidLogsDir}`);
-
- return;
-
+ if (totalTokens > 0) {
+ markdown += `**Total Tokens Used:** ${totalTokens.toLocaleString()}\n\n`;
}
-
- const files = fs.readdirSync(squidLogsDir).filter(file => file.endsWith(".log"));
-
- if (files.length === 0) {
-
- core.info(`No firewall log files found in: ${squidLogsDir}`);
-
- return;
-
- }
-
- core.info(`Found ${files.length} firewall log file(s)`);
-
- let totalRequests = 0;
-
- let allowedRequests = 0;
-
- let deniedRequests = 0;
-
- const allowedDomains = new Set();
-
- const deniedDomains = new Set();
-
- const requestsByDomain = new Map();
-
- for (const file of files) {
-
- const filePath = path.join(squidLogsDir, file);
-
- core.info(`Parsing firewall log: ${file}`);
-
- const content = fs.readFileSync(filePath, "utf8");
-
- const lines = content.split("\n").filter(line => line.trim());
-
- for (const line of lines) {
-
- const entry = parseFirewallLogLine(line);
-
- if (!entry) {
-
- continue;
-
- }
-
- totalRequests++;
-
- const isAllowed = isRequestAllowed(entry.decision, entry.status);
-
- if (isAllowed) {
-
- allowedRequests++;
-
- allowedDomains.add(entry.domain);
-
- } else {
-
- deniedRequests++;
-
- deniedDomains.add(entry.domain);
-
- }
-
- if (!requestsByDomain.has(entry.domain)) {
-
- requestsByDomain.set(entry.domain, { allowed: 0, denied: 0 });
-
- }
-
- const domainStats = requestsByDomain.get(entry.domain);
-
- if (isAllowed) {
-
- domainStats.allowed++;
-
- } else {
-
- domainStats.denied++;
-
- }
-
- }
-
+ const toolCalls = (logContent.match(/ToolCall:\s+\w+__\w+/g) || []).length;
+ if (toolCalls > 0) {
+ markdown += `**Tool Calls:** ${toolCalls}\n\n`;
}
-
- const summary = generateFirewallSummary({
-
- totalRequests,
-
- allowedRequests,
-
- deniedRequests,
-
- allowedDomains: Array.from(allowedDomains).sort(),
-
- deniedDomains: Array.from(deniedDomains).sort(),
-
- requestsByDomain,
-
- });
-
- core.summary.addRaw(summary).write();
-
- core.info("Firewall log summary generated successfully");
-
+ return markdown;
} catch (error) {
-
- core.setFailed(error instanceof Error ? error : String(error));
-
+ core.error(`Error parsing Codex log: ${error}`);
+ return "## 🤖 Commands and Tools\n\nError parsing log content.\n\n## 🤖 Reasoning\n\nUnable to parse reasoning from log.\n\n";
}
-
}
-
- function parseFirewallLogLine(line) {
-
- const trimmed = line.trim();
-
- if (!trimmed || trimmed.startsWith("#")) {
-
- return null;
-
+ function formatCodexToolCall(server, toolName, params, response, statusIcon) {
+ const totalTokens = estimateTokens(params) + estimateTokens(response);
+ let metadata = "";
+ if (totalTokens > 0) {
+ metadata = `~${totalTokens}t`;
}
-
- const fields = trimmed.match(/(?:[^\s"]+|"[^"]*")+/g);
-
- if (!fields || fields.length < 10) {
-
- return null;
-
+ const summary = `${server}::${toolName}`;
+ const sections = [];
+ if (params && params.trim()) {
+ sections.push({
+ label: "Parameters",
+ content: params,
+ language: "json",
+ });
}
-
- const timestamp = fields[0];
-
- if (!/^\d+(\.\d+)?$/.test(timestamp)) {
-
- return null;
-
+ if (response && response.trim()) {
+ sections.push({
+ label: "Response",
+ content: response,
+ language: "json",
+ });
}
-
- return {
-
- timestamp,
-
- clientIpPort: fields[1],
-
- domain: fields[2],
-
- destIpPort: fields[3],
-
- proto: fields[4],
-
- method: fields[5],
-
- status: fields[6],
-
- decision: fields[7],
-
- url: fields[8],
-
- userAgent: fields[9]?.replace(/^"|"$/g, "") || "-",
-
- };
-
+ return formatToolCallAsDetails({
+ summary,
+ statusIcon,
+ metadata,
+ sections,
+ });
}
-
- function isRequestAllowed(decision, status) {
-
- const statusCode = parseInt(status, 10);
-
- if (statusCode === 200 || statusCode === 206 || statusCode === 304) {
-
- return true;
-
- }
-
- if (decision.includes("TCP_TUNNEL") || decision.includes("TCP_HIT") || decision.includes("TCP_MISS")) {
-
- return true;
-
- }
-
- if (decision.includes("NONE_NONE") || decision.includes("TCP_DENIED") || statusCode === 403 || statusCode === 407) {
-
- return false;
-
+ function formatCodexBashCall(command, response, statusIcon) {
+ const totalTokens = estimateTokens(command) + estimateTokens(response);
+ let metadata = "";
+ if (totalTokens > 0) {
+ metadata = `~${totalTokens}t`;
}
-
- return false;
-
- }
-
- function generateFirewallSummary(analysis) {
-
- const { totalRequests, deniedRequests, deniedDomains, requestsByDomain } = analysis;
-
- let summary = "### 🔥 Firewall Blocked Requests\n\n";
-
- const validDeniedDomains = deniedDomains.filter(domain => domain !== "-");
-
- const validDeniedRequests = validDeniedDomains.reduce((sum, domain) => sum + (requestsByDomain.get(domain)?.denied || 0), 0);
-
- if (validDeniedRequests > 0) {
-
- summary += `**${validDeniedRequests}** request${validDeniedRequests !== 1 ? "s" : ""} blocked across **${validDeniedDomains.length}** unique domain${validDeniedDomains.length !== 1 ? "s" : ""}`;
-
- summary += ` (${totalRequests > 0 ? Math.round((validDeniedRequests / totalRequests) * 100) : 0}% of total traffic)\n\n`;
-
- summary += "\n";
-
- summary += "🚫 Blocked Domains (click to expand)
\n\n";
-
- summary += "| Domain | Blocked Requests |\n";
-
- summary += "|--------|------------------|\n";
-
- for (const domain of validDeniedDomains) {
-
- const stats = requestsByDomain.get(domain);
-
- summary += `| ${domain} | ${stats.denied} |\n`;
-
- }
-
- summary += "\n \n\n";
-
- } else {
-
- summary += "✅ **No blocked requests detected**\n\n";
-
- if (totalRequests > 0) {
-
- summary += `All ${totalRequests} request${totalRequests !== 1 ? "s" : ""} were allowed through the firewall.\n\n`;
-
- } else {
-
- summary += "No firewall activity detected.\n\n";
-
- }
-
+ const summary = `bash: ${truncateString(command, 60)}`;
+ const sections = [];
+ sections.push({
+ label: "Command",
+ content: command,
+ language: "bash",
+ });
+ if (response && response.trim()) {
+ sections.push({
+ label: "Output",
+ content: response,
+ });
}
-
- return summary;
-
+ return formatToolCallAsDetails({
+ summary,
+ statusIcon,
+ metadata,
+ sections,
+ });
}
-
if (typeof module !== "undefined" && module.exports) {
-
module.exports = {
-
- parseFirewallLogLine,
-
- isRequestAllowed,
-
- generateFirewallSummary,
-
- main,
-
+ parseCodexLog,
+ formatCodexToolCall,
+ formatCodexBashCall,
+ extractMCPInitialization,
};
-
- }
-
- const isDirectExecution =
-
- typeof module === "undefined" || (typeof require !== "undefined" && typeof require.main !== "undefined" && require.main === module);
-
- if (isDirectExecution) {
-
- main();
-
}
-
+ main();
- name: Upload Agent Stdio
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
@@ -5480,8 +4930,8 @@ jobs:
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
- GH_AW_AGENT_OUTPUT: /tmp/gh-aw/.agent/logs/
- GH_AW_ERROR_PATTERNS: "[{\"id\":\"\",\"pattern\":\"::(error)(?:\\\\s+[^:]*)?::(.+)\",\"level_group\":1,\"message_group\":2,\"description\":\"GitHub Actions workflow command - error\"},{\"id\":\"\",\"pattern\":\"::(warning)(?:\\\\s+[^:]*)?::(.+)\",\"level_group\":1,\"message_group\":2,\"description\":\"GitHub Actions workflow command - warning\"},{\"id\":\"\",\"pattern\":\"::(notice)(?:\\\\s+[^:]*)?::(.+)\",\"level_group\":1,\"message_group\":2,\"description\":\"GitHub Actions workflow command - notice\"},{\"id\":\"\",\"pattern\":\"(ERROR|Error):\\\\s+(.+)\",\"level_group\":1,\"message_group\":2,\"description\":\"Generic ERROR messages\"},{\"id\":\"\",\"pattern\":\"(WARNING|Warning):\\\\s+(.+)\",\"level_group\":1,\"message_group\":2,\"description\":\"Generic WARNING messages\"},{\"id\":\"\",\"pattern\":\"(\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}\\\\.\\\\d{3}Z)\\\\s+\\\\[(ERROR)\\\\]\\\\s+(.+)\",\"level_group\":2,\"message_group\":3,\"description\":\"Copilot CLI timestamped ERROR messages\"},{\"id\":\"\",\"pattern\":\"(\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}\\\\.\\\\d{3}Z)\\\\s+\\\\[(WARN|WARNING)\\\\]\\\\s+(.+)\",\"level_group\":2,\"message_group\":3,\"description\":\"Copilot CLI timestamped WARNING messages\"},{\"id\":\"\",\"pattern\":\"\\\\[(\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}\\\\.\\\\d{3}Z)\\\\]\\\\s+(CRITICAL|ERROR):\\\\s+(.+)\",\"level_group\":2,\"message_group\":3,\"description\":\"Copilot CLI bracketed critical/error messages with timestamp\"},{\"id\":\"\",\"pattern\":\"\\\\[(\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}\\\\.\\\\d{3}Z)\\\\]\\\\s+(WARNING):\\\\s+(.+)\",\"level_group\":2,\"message_group\":3,\"description\":\"Copilot CLI bracketed warning messages with timestamp\"},{\"id\":\"\",\"pattern\":\"✗\\\\s+(.+)\",\"level_group\":0,\"message_group\":1,\"description\":\"Copilot CLI failed command indicator\"},{\"id\":\"\",\"pattern\":\"(?:command not found|not found):\\\\s*(.+)|(.+):\\\\s*(?:command not found|not found)\",\"level_group\":0,\"message_group\":0,\"description\":\"Shell command not found error\"},{\"id\":\"\",\"pattern\":\"Cannot find module\\\\s+['\\\"](.+)['\\\"]\",\"level_group\":0,\"message_group\":1,\"description\":\"Node.js module not found error\"},{\"id\":\"\",\"pattern\":\"Permission denied and could not request permission from user\",\"level_group\":0,\"message_group\":0,\"description\":\"Copilot CLI permission denied warning (user interaction required)\"},{\"id\":\"\",\"pattern\":\"\\\\berror\\\\b.*permission.*denied\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied error (requires error context)\"},{\"id\":\"\",\"pattern\":\"\\\\berror\\\\b.*unauthorized\",\"level_group\":0,\"message_group\":0,\"description\":\"Unauthorized access error (requires error context)\"},{\"id\":\"\",\"pattern\":\"\\\\berror\\\\b.*forbidden\",\"level_group\":0,\"message_group\":0,\"description\":\"Forbidden access error (requires error context)\"}]"
+ GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log
+ GH_AW_ERROR_PATTERNS: "[{\"id\":\"\",\"pattern\":\"::(error)(?:\\\\s+[^:]*)?::(.+)\",\"level_group\":1,\"message_group\":2,\"description\":\"GitHub Actions workflow command - error\"},{\"id\":\"\",\"pattern\":\"::(warning)(?:\\\\s+[^:]*)?::(.+)\",\"level_group\":1,\"message_group\":2,\"description\":\"GitHub Actions workflow command - warning\"},{\"id\":\"\",\"pattern\":\"::(notice)(?:\\\\s+[^:]*)?::(.+)\",\"level_group\":1,\"message_group\":2,\"description\":\"GitHub Actions workflow command - notice\"},{\"id\":\"\",\"pattern\":\"(ERROR|Error):\\\\s+(.+)\",\"level_group\":1,\"message_group\":2,\"description\":\"Generic ERROR messages\"},{\"id\":\"\",\"pattern\":\"(WARNING|Warning):\\\\s+(.+)\",\"level_group\":1,\"message_group\":2,\"description\":\"Generic WARNING messages\"},{\"id\":\"\",\"pattern\":\"(\\\\d{4}-\\\\d{2}-\\\\d{2}T[\\\\d:.]+Z)\\\\s+(ERROR)\\\\s+(.+)\",\"level_group\":2,\"message_group\":3,\"description\":\"Codex ERROR messages with timestamp\"},{\"id\":\"\",\"pattern\":\"(\\\\d{4}-\\\\d{2}-\\\\d{2}T[\\\\d:.]+Z)\\\\s+(WARN|WARNING)\\\\s+(.+)\",\"level_group\":2,\"message_group\":3,\"description\":\"Codex warning messages with timestamp\"}]"
with:
script: |
function main() {
@@ -7066,7 +6516,8 @@ jobs:
GH_AW_UPDATE_TITLE: false
GH_AW_UPDATE_BODY: true
GH_AW_WORKFLOW_NAME: "Changeset Generator"
- GH_AW_ENGINE_ID: "copilot"
+ GH_AW_ENGINE_ID: "codex"
+ GH_AW_ENGINE_MODEL: "gpt-5-mini"
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
diff --git a/.github/workflows/changeset.md b/.github/workflows/changeset.md
index 117ff59684c..5eb6afd6dd2 100644
--- a/.github/workflows/changeset.md
+++ b/.github/workflows/changeset.md
@@ -12,7 +12,10 @@ permissions:
contents: read
pull-requests: read
issues: read
-engine: copilot
+engine:
+ id: codex
+ model: gpt-5-mini
+strict: false # Required: codex engine doesn't support network firewall
safe-outputs:
push-to-pull-request-branch:
commit-title-suffix: " [skip-ci]"
@@ -25,7 +28,6 @@ network:
allowed:
- defaults
- node
- firewall: true
tools:
bash:
- "*"
diff --git a/docs/src/content/docs/labs.mdx b/docs/src/content/docs/labs.mdx
index 826900eee16..42a800165cd 100644
--- a/docs/src/content/docs/labs.mdx
+++ b/docs/src/content/docs/labs.mdx
@@ -17,7 +17,7 @@ These are experimental agentic workflows used by the GitHub Next team to learn,
| [Blog Auditor](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/blog-auditor.md) | claude | [](https://github.com/githubnext/gh-aw/actions/workflows/blog-auditor.lock.yml) | `0 12 * * 3` | - |
| [Brave Web Search Agent](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/brave.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/brave.lock.yml) | - | `/brave` |
| [Breaking Change Checker](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/breaking-change-checker.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/breaking-change-checker.lock.yml) | `0 14 * * 1-5` | - |
-| [Changeset Generator](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/changeset.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/changeset.lock.yml) | - | - |
+| [Changeset Generator](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/changeset.md) | codex | [](https://github.com/githubnext/gh-aw/actions/workflows/changeset.lock.yml) | - | - |
| [CI Failure Doctor](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/ci-doctor.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/ci-doctor.lock.yml) | - | - |
| [Claude Command Processor - /cloclo](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/cloclo.md) | claude | [](https://github.com/githubnext/gh-aw/actions/workflows/cloclo.lock.yml) | - | `/cloclo` |
| [CLI Consistency Checker](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/cli-consistency-checker.md) | copilot | [](https://github.com/githubnext/gh-aw/actions/workflows/cli-consistency-checker.lock.yml) | `0 13 * * 1-5` | - |