diff --git a/app/index.js b/app/index.js index 81b4c3d..d67a4f0 100644 --- a/app/index.js +++ b/app/index.js @@ -2,7 +2,7 @@ import { loadInputs, logInputs } from './lib/inputs.js'; import { Git } from './lib/git.js'; import { GiteaClient } from './lib/gitea.js'; import { OpenCode } from './lib/opencode.js'; -import { log } from './lib/util.js'; +import { log, maskSecrets } from './lib/util.js'; async function main() { const inputs = loadInputs(); @@ -143,7 +143,9 @@ function buildResolveBranchName(target, source) { const runId = process.env.GITHUB_RUN_NUMBER || process.env.GITHUB_RUN_ID || ''; const safe = (s) => s.replace(/[^a-zA-Z0-9._/-]/g, '-'); const suffix = runId ? `-${runId}` : ''; - return `resolve-conflict/${safe(target)}-into-${safe(source)}${suffix}`; + // 截斷主體長度,避免 target/source 過長使分支名稱超出 Git 限制 + const stem = `${safe(target)}-into-${safe(source)}`.slice(0, 180); + return `resolve-conflict/${stem}${suffix}`; } /** 解衝突 PR 的描述。 */ @@ -180,6 +182,8 @@ function reportPull(pull, created) { } main().catch((err) => { - log.error(err?.stack || err?.message || String(err)); + const detail = err?.stack || err?.message || String(err); + // 錯誤訊息/stack 可能夾帶 token,輸出到 CI 日誌前先遮蔽 + log.error(maskSecrets(detail, [process.env.GITEA_TOKEN])); process.exit(1); }); diff --git a/app/lib/opencode.js b/app/lib/opencode.js index 1190790..0d3870f 100644 --- a/app/lib/opencode.js +++ b/app/lib/opencode.js @@ -1,6 +1,6 @@ -import { writeFileSync, mkdtempSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; +import { writeFileSync, mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir, homedir } from 'node:os'; +import { join, dirname } from 'node:path'; import { run, log, maskSecrets } from './util.js'; /** @@ -74,33 +74,38 @@ export class OpenCode { const configPath = this._writeConfig(); const prompt = buildPrompt({ ...ctx, language: this.language }); - log.info(`呼叫 opencode(${this.provider}/${this.model})分析 diff...`); - const result = run( - 'opencode', - ['run', '--model', `${this.provider}/${this.model}`, prompt], - { - cwd: tmpdir(), - env: { - ...process.env, - OPENCODE_CONFIG: configPath, - // 確保 opencode 有可寫的 HOME / 設定目錄 - HOME: process.env.HOME || '/root', + try { + log.info(`呼叫 opencode(${this.provider}/${this.model})分析 diff...`); + const result = run( + 'opencode', + ['run', '--model', `${this.provider}/${this.model}`, prompt], + { + cwd: tmpdir(), + env: { + ...process.env, + OPENCODE_CONFIG: configPath, + // 確保 opencode 有可寫的 HOME / 設定目錄 + HOME: process.env.HOME || homedir(), + }, + timeout: 5 * 60 * 1000, }, - timeout: 5 * 60 * 1000, - }, - ); + ); - if (result.status !== 0) { - log.warn(`opencode 執行失敗 (${result.status}):${maskSecrets(result.stderr).slice(0, 500)}`); - return null; - } + if (result.status !== 0) { + log.warn(`opencode 執行失敗 (${result.status}):${maskSecrets(result.stderr).slice(0, 500)}`); + return null; + } - const parsed = extractResult(result.stdout); - if (!parsed) { - log.warn('無法從 opencode 輸出解析出標題/描述'); - return null; + const parsed = extractResult(result.stdout); + if (!parsed) { + log.warn('無法從 opencode 輸出解析出標題/描述'); + return null; + } + return parsed; + } finally { + // 清理 _writeConfig 建立的暫存設定目錄,避免堆積 + rmSync(dirname(configPath), { recursive: true, force: true }); } - return parsed; } }