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
This commit is contained in:
mastertyko 2026-03-27 03:33:20 +01:00 committed by GitHub
parent 9823fd2d2d
commit 9ce6e02bd8
2 changed files with 47 additions and 0 deletions

View file

@ -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<void> {
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}`);

View file

@ -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 () => {