mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-10 01:48:38 +02:00
feat: agent-sdk-runner spawns hermetic children via complete Options.env
The historical 'env: breaks SDK auth' failure was partial-env replacement: Options.env replaces the child's entire environment, so objects lacking ANTHROPIC_API_KEY killed auth. Passing the complete hermetic env (key + PATH + redirected CLAUDE_CONFIG_DIR/GSTACK_HOME) works — validated live via query() with a Bash tool call (success, real cost, Conductor vars scrubbed). Per-test opts.env merges last; ambient key mutation still works because the builder reads process.env at call time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -347,7 +347,13 @@ describe('runAgentSdkTest — options propagation', () => {
|
|||||||
expect(opts.permissionMode).toBe('bypassPermissions');
|
expect(opts.permissionMode).toBe('bypassPermissions');
|
||||||
expect(opts.allowDangerouslySkipPermissions).toBe(true);
|
expect(opts.allowDangerouslySkipPermissions).toBe(true);
|
||||||
expect(opts.settingSources).toEqual([]);
|
expect(opts.settingSources).toEqual([]);
|
||||||
expect(opts.env).toEqual({ ANTHROPIC_API_KEY: 'fake' });
|
// env is the COMPLETE hermetic env with the per-test override merged
|
||||||
|
// last — partial pass-through was the documented SDK auth-breaker
|
||||||
|
// (Options.env replaces the child's entire environment).
|
||||||
|
expect(opts.env?.ANTHROPIC_API_KEY).toBe('fake');
|
||||||
|
expect(opts.env?.PATH).toBeTruthy();
|
||||||
|
expect(opts.env?.CLAUDE_CONFIG_DIR).toMatch(/\/\.claude$/);
|
||||||
|
expect(opts.env?.GSTACK_HOME).toContain('gstack-home');
|
||||||
expect(opts.pathToClaudeCodeExecutable).toBe('/fake/path/claude');
|
expect(opts.pathToClaudeCodeExecutable).toBe('/fake/path/claude');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { resolveClaudeBinary as resolveClaudeBinaryShared } from '../../browse/src/claude-bin';
|
import { resolveClaudeBinary as resolveClaudeBinaryShared } from '../../browse/src/claude-bin';
|
||||||
|
import { hermeticChildEnv } from './hermetic-env';
|
||||||
import type { SkillTestResult } from './session-runner';
|
import type { SkillTestResult } from './session-runner';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -300,12 +301,17 @@ export async function runAgentSdkTest(
|
|||||||
const queryImpl: QueryProvider = opts.queryProvider ?? query;
|
const queryImpl: QueryProvider = opts.queryProvider ?? query;
|
||||||
const model = opts.model ?? 'claude-opus-4-7';
|
const model = opts.model ?? 'claude-opus-4-7';
|
||||||
|
|
||||||
// NOTE on GSTACK_HEADLESS: the SDK child inherits process.env, so headless
|
// NOTE on env: the SDK child gets the COMPLETE hermetic env (allowlist
|
||||||
// classification for eval/E2E runs is set by the `test:gate` / `test:evals`
|
// scrub + ANTHROPIC_API_KEY + hermetic CLAUDE_CONFIG_DIR/GSTACK_HOME), with
|
||||||
// package.json scripts (scoped to that invocation), NOT mutated here. We must not
|
// per-test opts.env merging last. The historical "passing env: breaks SDK
|
||||||
// pass sdkOpts.env (it breaks the SDK auth pipeline — see CLAUDE.md) and must not
|
// auth" failure (old CLAUDE.md warning) was partial-env replacement —
|
||||||
// mutate process.env ambiently (it would leak headless into later interactive-path
|
// Options.env REPLACES the child's entire environment, so an object without
|
||||||
// tests in the same Bun process — Codex review finding).
|
// the key killed auth. A complete env is safe (validated 2026-06-12 via
|
||||||
|
// query() with hermeticChildEnv(): success, real cost, Bash tool working).
|
||||||
|
// Do not mutate process.env ambiently here (it would leak into later
|
||||||
|
// interactive-path tests in the same Bun process — Codex review finding);
|
||||||
|
// ambient ANTHROPIC_API_KEY mutation by tests still works because the
|
||||||
|
// builder reads process.env at call time.
|
||||||
|
|
||||||
let attempt = 0;
|
let attempt = 0;
|
||||||
let lastErr: unknown = null;
|
let lastErr: unknown = null;
|
||||||
@@ -356,7 +362,7 @@ export async function runAgentSdkTest(
|
|||||||
permissionMode: resolvedPermissionMode,
|
permissionMode: resolvedPermissionMode,
|
||||||
allowDangerouslySkipPermissions: resolvedPermissionMode === 'bypassPermissions',
|
allowDangerouslySkipPermissions: resolvedPermissionMode === 'bypassPermissions',
|
||||||
settingSources: opts.settingSources ?? [],
|
settingSources: opts.settingSources ?? [],
|
||||||
env: opts.env,
|
env: hermeticChildEnv(opts.env),
|
||||||
pathToClaudeCodeExecutable: opts.pathToClaudeCodeExecutable,
|
pathToClaudeCodeExecutable: opts.pathToClaudeCodeExecutable,
|
||||||
...(hasCanUseTool ? { canUseTool: opts.canUseTool } : {}),
|
...(hasCanUseTool ? { canUseTool: opts.canUseTool } : {}),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user