From 2b96b1a247ee57faa89d865ec93ccaa42ba2b991 Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Fri, 13 Mar 2026 23:07:01 -0600 Subject: [PATCH 1/3] fix: re-check secrets manifest before every dispatch, not just at startAuto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When plan-milestone writes SECRETS-MANIFEST.md, the secrets gate at startAuto has already fired (manifest didn't exist yet). Without a re-check, the model proceeds into plan-slice / execute-task with no real credentials and mocks external services — cascading risk into every downstream slice. Added a secrets re-check gate inside dispatchNextUnit that runs before every unit dispatch. getManifestStatus is cheap (file read + .env scan) so the check is a no-op when no manifest exists or all secrets are already collected. Idempotent: skips keys already present in .env. Closes #303 --- src/resources/extensions/gsd/auto.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index c766829a0..8669ba193 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -1176,6 +1176,27 @@ async function dispatchNextUnit( } } + // ── Secrets re-check gate — runs before every dispatch, not just at startAuto ── + // plan-milestone writes SECRETS-MANIFEST.md during its unit. By the time we + // reach the next dispatchNextUnit call the manifest exists but hasn't been + // presented to the user yet. Without this re-check the model would proceed + // into plan-slice / execute-task with no real credentials and mock everything. + try { + const manifestStatus = await getManifestStatus(basePath, mid); + if (manifestStatus && manifestStatus.pending.length > 0) { + const result = await collectSecretsFromManifest(basePath, mid, ctx); + ctx.ui.notify( + `Secrets collected: ${result.applied.length} applied, ${result.skipped.length} skipped, ${result.existingSkipped.length} already set.`, + "info", + ); + } + } catch (err) { + ctx.ui.notify( + `Secrets collection error: ${err instanceof Error ? err.message : String(err)}`, + "warning", + ); + } + const needsRunUat = await checkNeedsRunUat(basePath, mid, state, prefs); // Flag: for human/mixed UAT, pause auto-mode after the prompt is sent so the user // can perform the UAT manually. On next resume, result file will exist → skip. From 0810aec739b53f61028d93fc9645daaceaf93cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Fri, 13 Mar 2026 23:12:49 -0600 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/resources/extensions/gsd/auto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index 8669ba193..5ec41aab5 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -1177,7 +1177,7 @@ async function dispatchNextUnit( } // ── Secrets re-check gate — runs before every dispatch, not just at startAuto ── - // plan-milestone writes SECRETS-MANIFEST.md during its unit. By the time we + // plan-milestone writes the milestone SECRETS file (e.g., M001-SECRETS.md) during its unit. By the time we // reach the next dispatchNextUnit call the manifest exists but hasn't been // presented to the user yet. Without this re-check the model would proceed // into plan-slice / execute-task with no real credentials and mock everything. From 9ef7536f3d6be56830c24767abe511104bc6e7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Fri, 13 Mar 2026 23:13:06 -0600 Subject: [PATCH 3/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/resources/extensions/gsd/auto.ts | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index 5ec41aab5..cfdc36952 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -1181,21 +1181,25 @@ async function dispatchNextUnit( // reach the next dispatchNextUnit call the manifest exists but hasn't been // presented to the user yet. Without this re-check the model would proceed // into plan-slice / execute-task with no real credentials and mock everything. - try { - const manifestStatus = await getManifestStatus(basePath, mid); - if (manifestStatus && manifestStatus.pending.length > 0) { - const result = await collectSecretsFromManifest(basePath, mid, ctx); + const runSecretsGate = async () => { + try { + const manifestStatus = await getManifestStatus(basePath, mid); + if (manifestStatus && manifestStatus.pending.length > 0) { + const result = await collectSecretsFromManifest(basePath, mid, ctx); + ctx.ui.notify( + `Secrets collected: ${result.applied.length} applied, ${result.skipped.length} skipped, ${result.existingSkipped.length} already set.`, + "info", + ); + } + } catch (err) { ctx.ui.notify( - `Secrets collected: ${result.applied.length} applied, ${result.skipped.length} skipped, ${result.existingSkipped.length} already set.`, - "info", + `Secrets collection error: ${err instanceof Error ? err.message : String(err)}`, + "warning", ); } - } catch (err) { - ctx.ui.notify( - `Secrets collection error: ${err instanceof Error ? err.message : String(err)}`, - "warning", - ); - } + }; + + await runSecretsGate(); const needsRunUat = await checkNeedsRunUat(basePath, mid, state, prefs); // Flag: for human/mixed UAT, pause auto-mode after the prompt is sent so the user