From 73391456417254c2e627c4cb7fda3bf3e4501520 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Tue, 16 Jun 2026 11:40:58 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20withAskpass=20=E7=AD=89=E5=BE=85?= =?UTF-8?q?=E9=9D=9E=E5=90=8C=E6=AD=A5=20callback=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E6=89=8D=E6=B8=85=E7=90=86=20askpass=20=E8=85=B3=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commitAndPush 傳入 async callback,但 withAskpass 是同步 try/finally, 會在第一個 await(LLM 合併指令檔)時就刪除 .git-askpass.sh,導致後續 git push 因 GIT_ASKPASS 指向已刪除腳本而失敗(cannot exec .git-askpass.sh / could not read Username)。前置驗證的 verifyRemoteAccess 用同步 callback 所以 ls-remote 通過,造成前置驗證過但 push 失敗的落差。 改為當 callback 回傳 thenable 時以 result.finally(cleanup) 延後清理, 同步 callback 維持立即清理與原樣回傳,不影響 verifyRemoteAccess / cloneRepo。 新增回歸測試斷言 git push 執行當下 askpass 腳本仍存在。 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/git.js | 17 ++++++++++++++--- app/git.test.js | 12 ++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/git.js b/app/git.js index acaa58e..38c5582 100644 --- a/app/git.js +++ b/app/git.js @@ -62,11 +62,22 @@ function withAskpass(workspace, fn) { const askpassScript = path.join(workspace, '.git-askpass.sh'); fs.writeFileSync(askpassScript, '#!/bin/sh\necho "$GIT_TOKEN"\n', { mode: 0o700 }); const credEnv = { ...process.env, GIT_ASKPASS: askpassScript, GIT_USERNAME: 'x-token', GIT_TOKEN: GITEA_TOKEN }; + const cleanup = () => { try { fs.unlinkSync(askpassScript); } catch {} }; + let result; try { - return fn(credEnv); - } finally { - try { fs.unlinkSync(askpassScript); } catch {} + result = fn(credEnv); + } catch (e) { + cleanup(); + throw e; } + // Defer cleanup until an async callback settles, otherwise the askpass script + // is deleted at the first `await` and later network ops (e.g. git push) fail + // with "cannot exec .git-askpass.sh". Sync callbacks clean up immediately. + if (result && typeof result.then === 'function') { + return result.finally(cleanup); + } + cleanup(); + return result; } function readGitOutput(run, args, cwd, env) { diff --git a/app/git.test.js b/app/git.test.js index 0ebb723..5d92566 100644 --- a/app/git.test.js +++ b/app/git.test.js @@ -93,6 +93,18 @@ describe('commitAndPush', () => { } }); + it('keeps the askpass script present while the network push runs', async () => { + let askpassExistsAtPush = null; + const spawn = makeSpawn({ + push: (_args, opts) => { + askpassExistsAtPush = !!(opts?.env?.GIT_ASKPASS && fs.existsSync(opts.env.GIT_ASKPASS)); + return { status: 0, stdout: '', stderr: '', error: null }; + }, + }); + await commitAndPush(workspace, path.join(workspace, 'repo'), spawn, sourceRoot); + assert.equal(askpassExistsAtPush, true, 'askpass script must still exist when git push runs'); + }); + it('cleans up askpass script after successful run', async () => { await commitAndPush(workspace, path.join(workspace, 'repo'), makeSpawn(), sourceRoot); const leftover = fs.readdirSync(workspace).filter(f => f.endsWith('.git-askpass.sh'));