From 9ce6e02bd8e2c57568ad4cb861ee7488c6d378f9 Mon Sep 17 00:00:00 2001 From: mastertyko <11311479+mastertyko@users.noreply.github.com> Date: Fri, 27 Mar 2026 03:33:20 +0100 Subject: [PATCH] fix: hydrate collected secrets for current session (#2788) secure_env_collect previously persisted secrets to their destination but left the running Node process unchanged. Extensions like Context7 read process.env directly, so newly collected keys did not work until restart. Hydrate process.env as soon as a secret is successfully applied, and cover the regression through collectSecretsFromManifest so the current session can use the key immediately. Closes #2685 --- .../extensions/get-secrets-from-user.ts | 8 ++++ .../gsd/tests/collect-from-manifest.test.ts | 39 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/resources/extensions/get-secrets-from-user.ts b/src/resources/extensions/get-secrets-from-user.ts index 9ff6cbb03..300852305 100644 --- a/src/resources/extensions/get-secrets-from-user.ts +++ b/src/resources/extensions/get-secrets-from-user.ts @@ -47,6 +47,12 @@ function shellEscapeSingle(value: string): string { return `'${value.replace(/'/g, `'\\''`)}'`; } +function hydrateProcessEnv(key: string, value: string): void { + // Make newly collected secrets immediately visible to the current session. + // Some extensions read process.env directly and do not reload .env on every call. + process.env[key] = value; +} + async function writeEnvKey(filePath: string, key: string, value: string): Promise { let content = ""; try { @@ -312,6 +318,7 @@ async function applySecrets( try { await writeEnvKey(opts.envFilePath, key, value); applied.push(key); + hydrateProcessEnv(key, value); } catch (err: any) { errors.push(`${key}: ${err.message}`); } @@ -330,6 +337,7 @@ async function applySecrets( errors.push(`${key}: ${result.stderr.slice(0, 200)}`); } else { applied.push(key); + hydrateProcessEnv(key, value); } } catch (err: any) { errors.push(`${key}: ${err.message}`); diff --git a/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts b/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts index c0a62946f..9ca2eecd9 100644 --- a/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +++ b/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts @@ -227,6 +227,45 @@ test("collectSecretsFromManifest: manifest statuses are updated after collection "KEY_TO_SKIP should have status 'skipped' after user skipped it"); }); +test("collectSecretsFromManifest: applied keys hydrate process.env for the running session", async (t) => { + const { collectSecretsFromManifest } = await loadOrchestrator(); + + const tmp = makeTempDir("manifest-live-env"); + const envKey = "CONTEXT7_API_KEY"; + const saved = process.env[envKey]; + t.after(() => { + if (saved === undefined) delete process.env[envKey]; + else process.env[envKey] = saved; + rmSync(tmp, { recursive: true, force: true }); + }); + + delete process.env[envKey]; + + const manifest = makeManifest([ + { key: envKey, status: "pending" }, + ]); + await writeManifestFile(tmp, manifest); + + let callIndex = 0; + const mockCtx = { + cwd: tmp, + hasUI: true, + ui: { + custom: async (_factory: any) => { + callIndex++; + if (callIndex <= 1) return null; // summary screen dismiss + return "c7_live_test_key"; + }, + }, + }; + + const result = await collectSecretsFromManifest(tmp, "M001", mockCtx as any); + + assert.ok(result.applied.includes(envKey), "CONTEXT7_API_KEY should be applied"); + assert.equal(process.env[envKey], "c7_live_test_key", + "applied keys should be available through process.env without restarting"); +}); + // ─── showSecretsSummary: render output ──────────────────────────────────────── test("showSecretsSummary: produces lines with correct status glyphs for each entry status", async () => {