Compare commits
3
Commits
develop
...
98aa4f33fd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98aa4f33fd | ||
|
|
0774e6c7c1 | ||
|
|
560e6e1d35 |
@@ -1 +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",
|
||||
"location": "app/main.js:248",
|
||||
"problem": "在 `resolve` 函式的回傳值邏輯中使用了 `lastMessage || output.toString()`,這種基於真值判斷(truthiness)的邏輯若 `lastMessage` 為空字串但 `output` 亦為空,會導致語意不明,且與前面處理錯誤的情境交織,增加閱讀複雜度。",
|
||||
"suggestion": "建議明確區分 `lastMessage` 的來源,若確定檔案應存在,請使用更明確的判斷方式(如檢查檔案是否存在或長度),提升程式碼的可預測性。",
|
||||
"is_new": true
|
||||
}
|
||||
]
|
||||
|
||||
+18
-4
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user