From cab7507ddb0ba58a009d53d840541068910332b5 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 14:13:07 +0000 Subject: [PATCH 1/5] =?UTF-8?q?fix(app):=20=E5=83=85=E8=BC=B8=E5=87=BA=20C?= =?UTF-8?q?odex=20=E6=9C=80=E5=BE=8C=E5=9B=9E=E8=A6=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/app/main.js b/app/main.js index de5feed..d87f06a 100644 --- a/app/main.js +++ b/app/main.js @@ -183,11 +183,13 @@ function readExecutionConfig() { }; } -function codexExecArgs(model, prompt) { +function codexExecArgs(model, prompt, lastMessageFile) { return [ "exec", "--dangerously-bypass-approvals-and-sandbox", "--skip-git-repo-check", + "--output-last-message", + lastMessageFile, "--model", model, prompt, @@ -197,7 +199,10 @@ function codexExecArgs(model, prompt) { function runCodex(model, prompt) { return new Promise((resolve) => { const { timeoutMs, outputLimitBytes, workspace } = readExecutionConfig(); - const args = codexExecArgs(model, prompt); + const tempDir = makeTempDir(process.env.CODEX_HOME || "/root/.codex"); + const lastMessageFile = path.join(tempDir, "last-message.txt"); + tempFiles.trackFile(lastMessageFile); + const args = codexExecArgs(model, prompt, lastMessageFile); // This Docker Action runs inside an ephemeral CI container where Codex must be // able to edit the checked-out workspace without interactive approvals. @@ -210,7 +215,6 @@ function runCodex(model, prompt) { output.append(Buffer.from(`Codex execution timed out after ${timeoutMs} ms.\n`)); }, timeoutMs); - child.stdout.pipe(process.stdout); child.stderr.pipe(process.stdout); child.stdout.on("data", (chunk) => { @@ -235,7 +239,14 @@ function runCodex(model, prompt) { output.append(Buffer.from(`Codex process terminated by signal ${signal}.\n`)); } - resolve({ status: code ?? 1, output: output.toString() }); + let lastMessage = ""; + try { + lastMessage = fs.readFileSync(lastMessageFile, "utf8"); + } catch { + lastMessage = ""; + } + + resolve({ status: code ?? 1, output: lastMessage || output.toString() }); }); }); } @@ -312,6 +323,9 @@ function setupAuth(oauth, codexHome) { async function runCodexAction({ oauth, model, codexHome, prompt }) { const lockHandle = setupAuth(oauth, codexHome); const result = await runCodex(model, prompt); + if (result.output) { + process.stdout.write(result.output.endsWith("\n") ? result.output : `${result.output}\n`); + } appendGithubOutput(result.status === 0 ? "completed" : "failed", result.output); if (lockHandle !== undefined) { From ffdb53c645c9ec13fbe9cebd03df39965406f76e Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 14:13:10 +0000 Subject: [PATCH 2/5] =?UTF-8?q?test(entrypoint):=20=E8=A6=86=E8=93=8B=20Co?= =?UTF-8?q?dex=20transcript=20=E9=87=8D=E8=A4=87=E8=BC=B8=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/entrypoint_test.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/entrypoint_test.sh b/tests/entrypoint_test.sh index 85983cd..97155ee 100755 --- a/tests/entrypoint_test.sh +++ b/tests/entrypoint_test.sh @@ -56,6 +56,26 @@ SH chmod +x "$dir/codex" } +make_fake_transcript_codex() { + local dir="$1" + + cat > "$dir/codex" <<'SH' +#!/bin/sh +last_message_file="" +while [ "$#" -gt 0 ]; do + if [ "$1" = "--output-last-message" ]; then + shift + last_message_file="$1" + fi + shift +done + +printf '%s\n' 'codex' 'final answer' 'tokens used' '123' 'final answer' +printf '%s\n' 'final answer' > "$last_message_file" +SH + chmod +x "$dir/codex" +} + encoded_json() { printf '%s' "$1" | base64 | tr -d '\n' } @@ -269,6 +289,20 @@ test_codex_output_truncation() { rm -rf "$tmpdir" } +test_last_message_output_without_transcript_duplicate() { + local tmpdir + tmpdir="$(mktemp -d)" + make_fake_transcript_codex "$tmpdir" + + run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="hello" + + [[ "$(grep -c "final answer" "$tmpdir/stdout")" -eq 1 ]] || die "stdout printed final answer more than once" + grep -q "status=completed" "$tmpdir/github-output" || die "last message completed status" + [[ "$(grep -c "final answer" "$tmpdir/github-output")" -eq 1 ]] || die "github output included transcript duplicate" + ! grep -q "tokens used" "$tmpdir/github-output" || die "github output included codex transcript" + rm -rf "$tmpdir" +} + test_invalid_execution_config_uses_defaults() { local tmpdir tmpdir="$(mktemp -d)" @@ -299,6 +333,7 @@ test_missing_codex_command_output test_codex_timeout_output test_codex_signal_output test_codex_output_truncation +test_last_message_output_without_transcript_duplicate test_invalid_execution_config_uses_defaults echo "entrypoint tests passed" From 49061fe03363bd5e1bb4fabc36dffad0eb8b0ba3 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 14:15:09 +0000 Subject: [PATCH 3/5] chore: update ai-review findings [ai-review-bot][success] --- .gitea/ai-review/findings.json | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index b3397bd..5e6a082 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,10 +1,25 @@ [ + { + "level": "warning", + "role": "Bard", + "location": "app/main.js:245", + "problem": "在 `runCodex` 函式中,對 `lastMessageFile` 的讀取缺乏明確的錯誤處理策略,直接將錯誤靜默處理並初始化為空字串,可能掩蓋真正的檔案系統問題,且導致邏輯錯誤。", + "suggestion": "建議在 `catch` 區塊中至少加入簡單的日誌記錄(如 `console.error`),並明確區分錯誤類型(如檔案不存在 vs 權限問題),給予清晰的回饋。" + }, + { + "level": "info", + "role": "Rogue", + "location": "app/main.js:200", + "problem": "每次執行 runCodex 都在建立新的 temp 目錄與追蹤檔案,這種重複性的檔案系統配置開銷對於高頻率呼叫來說是完全浪費的資源。", + "suggestion": "如果執行環境是持續存在的,應快取 tempDir 路徑或使用預設的共享路徑,避免頻繁的檔案系統建立與追蹤開銷。", + "is_new": true + }, { "level": "info", "role": "Bard", - "problem": "setupAuth 函式職責過於繁雜。", - "suggestion": "將鎖定機制與驗證機制拆分為獨立輔助函式。", - "location": "app/main.js:283", - "is_new": false + "location": "app/main.js:248", + "problem": "在 `resolve` 函式的回傳值邏輯中使用了 `lastMessage || output.toString()`,這種基於真值判斷(truthiness)的邏輯若 `lastMessage` 為空字串但 `output` 亦為空,會導致語意不明,且與前面處理錯誤的情境交織,增加閱讀複雜度。", + "suggestion": "建議明確區分 `lastMessage` 的來源,若確定檔案應存在,請使用更明確的判斷方式(如檢查檔案是否存在或長度),提升程式碼的可預測性。", + "is_new": true } ] From ab56c56206c338d1d58c90d6f0f01e0c66e362fc Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 24 Jun 2026 14:22:38 +0000 Subject: [PATCH 4/5] =?UTF-8?q?fix(app):=20=E5=81=9C=E6=AD=A2=E4=B8=B2?= =?UTF-8?q?=E6=B5=81=20Codex=20transcript?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.js | 13 ++++--------- tests/entrypoint_test.sh | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/main.js b/app/main.js index d87f06a..0687011 100644 --- a/app/main.js +++ b/app/main.js @@ -215,8 +215,6 @@ function runCodex(model, prompt) { output.append(Buffer.from(`Codex execution timed out after ${timeoutMs} ms.\n`)); }, timeoutMs); - child.stderr.pipe(process.stdout); - child.stdout.on("data", (chunk) => { output.append(chunk); }); @@ -239,14 +237,11 @@ function runCodex(model, prompt) { output.append(Buffer.from(`Codex process terminated by signal ${signal}.\n`)); } - let lastMessage = ""; - try { - lastMessage = fs.readFileSync(lastMessageFile, "utf8"); - } catch { - lastMessage = ""; - } + const lastMessage = fs.existsSync(lastMessageFile) ? fs.readFileSync(lastMessageFile, "utf8") : null; + const diagnosticOutput = output.toString(); + const resultOutput = code === 0 && lastMessage !== null ? lastMessage : diagnosticOutput || lastMessage || ""; - resolve({ status: code ?? 1, output: lastMessage || output.toString() }); + resolve({ status: code ?? 1, output: resultOutput }); }); }); } diff --git a/tests/entrypoint_test.sh b/tests/entrypoint_test.sh index 97155ee..613d43a 100755 --- a/tests/entrypoint_test.sh +++ b/tests/entrypoint_test.sh @@ -70,7 +70,7 @@ while [ "$#" -gt 0 ]; do shift done -printf '%s\n' 'codex' 'final answer' 'tokens used' '123' 'final answer' +printf '%s\n' 'codex' 'final answer' 'tokens used' '123' 'final answer' >&2 printf '%s\n' 'final answer' > "$last_message_file" SH chmod +x "$dir/codex" From 9f7a2e97c3c41a9ede45221bc1cb363be2e48752 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Wed, 24 Jun 2026 14:23:08 +0000 Subject: [PATCH 5/5] chore: update ai-review findings [ai-review-bot][success] --- .gitea/ai-review/findings.json | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 5e6a082..211334a 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -2,24 +2,15 @@ { "level": "warning", "role": "Bard", - "location": "app/main.js:245", - "problem": "在 `runCodex` 函式中,對 `lastMessageFile` 的讀取缺乏明確的錯誤處理策略,直接將錯誤靜默處理並初始化為空字串,可能掩蓋真正的檔案系統問題,且導致邏輯錯誤。", - "suggestion": "建議在 `catch` 區塊中至少加入簡單的日誌記錄(如 `console.error`),並明確區分錯誤類型(如檔案不存在 vs 權限問題),給予清晰的回饋。" - }, - { - "level": "info", - "role": "Rogue", - "location": "app/main.js:200", - "problem": "每次執行 runCodex 都在建立新的 temp 目錄與追蹤檔案,這種重複性的檔案系統配置開銷對於高頻率呼叫來說是完全浪費的資源。", - "suggestion": "如果執行環境是持續存在的,應快取 tempDir 路徑或使用預設的共享路徑,避免頻繁的檔案系統建立與追蹤開銷。", - "is_new": true + "location": "app/main.js:241", + "problem": "`runCodex` 中 `resultOutput` 的計算邏輯過於緊湊且不易維護,且直接進行檔案同步讀取,若檔案過大可能阻塞 Node.js 事件循環。", + "suggestion": "建議拆解複雜的邏輯為獨立的輔助函數,並改用非同步的檔案讀取方式。" }, { "level": "info", "role": "Bard", - "location": "app/main.js:248", - "problem": "在 `resolve` 函式的回傳值邏輯中使用了 `lastMessage || output.toString()`,這種基於真值判斷(truthiness)的邏輯若 `lastMessage` 為空字串但 `output` 亦為空,會導致語意不明,且與前面處理錯誤的情境交織,增加閱讀複雜度。", - "suggestion": "建議明確區分 `lastMessage` 的來源,若確定檔案應存在,請使用更明確的判斷方式(如檢查檔案是否存在或長度),提升程式碼的可預測性。", - "is_new": true + "location": "app/main.js:321", + "problem": "確保字串結尾換行的樣板程式碼邏輯重複出現。", + "suggestion": "建立 `ensureNewline` 共用工具函式。" } ]