* fix(#2832): gsd-sdk auto detects Codex runtime correctly Two-part fix for #2832 (gsd-sdk auto silently routing non-Claude runtime projects through the Claude Agent SDK): 1. Runtime gate at the `auto` entry point. New `runtime-gate.ts` exports `assertRuntimeSupportsAutoMode(config)` which throws an actionable error when `GSD_RUNTIME` / `config.runtime` resolves to a non-Claude runtime (codex, gemini, opencode, etc.). The autonomous orchestrator only knows how to drive `@anthropic-ai/claude-agent-sdk` today; failing fast with a clear pointer at the in-session slash commands beats the previous instant `[FAILED] $0.00 0.1s` flake. Wired into `cli.ts` before the GSD/InitRunner construction. 2. Runtime-aware `resolveModel()` in `session-runner.ts`. The profile -> id map (`balanced -> claude-sonnet-4-6`, etc.) was applied unconditionally, so even with `runtime: codex` and `resolve_model_ids: omit` the SDK forced a Claude id into `query()`. Now the profile map only fires when the runtime is Claude and the explicit `resolve_model_ids: "omit"` knob short-circuits to undefined, mirroring `query/config-query.ts`. Tests (vitest, sdk/src): - runtime-gate.test.ts (8 cases): claude / unset / unknown pass; codex, gemini, opencode throw; GSD_RUNTIME wins over config.runtime; error message references #2832 and the slash-command workaround. - session-runner.test.ts (4 new cases under "resolveModel runtime awareness (#2832)"): codex runtime + balanced profile -> no model injected; resolve_model_ids: omit -> no model; claude runtime still resolves to claude-sonnet-4-6 (no regression); explicit options.model wins on any runtime. * fix(#2832): address CR — env-precedence in resolveModel + accurate source attribution Two CodeRabbit findings on PR #2844: 1. session-runner.ts:resolveModel() (Major) — read runtime via detectRuntime() so GSD_RUNTIME env precedence is honored. Without this, a Codex run with a Claude-shaped config still fell into the Claude-only profile-id branch. 2. runtime-gate.ts:assertRuntimeSupportsAutoMode() (Minor) — when GSD_RUNTIME holds an unsupported value, detectRuntime() falls through to config but the source label still reported the discarded env value. Fix: validate env against SUPPORTED_RUNTIMES before attributing the source. Tests added for both: env-precedence in session-runner, source attribution in runtime-gate. 17/17 pass.
85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
/**
|
|
* Unit tests for runtime-gate.ts
|
|
*
|
|
* Regression tests for #2832: gsd-sdk auto silently routed Codex (and other
|
|
* non-Claude) runtime projects through the Claude Agent SDK, picked
|
|
* Claude-Sonnet defaults from the profile map, and reported instant failures.
|
|
* The gate fails fast with an actionable error so users either set the right
|
|
* runtime or fall back to the in-session GSD slash commands.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { assertRuntimeSupportsAutoMode } from './runtime-gate.js';
|
|
|
|
describe('assertRuntimeSupportsAutoMode', () => {
|
|
let prevEnv: string | undefined;
|
|
beforeEach(() => {
|
|
prevEnv = process.env.GSD_RUNTIME;
|
|
delete process.env.GSD_RUNTIME;
|
|
});
|
|
afterEach(() => {
|
|
if (prevEnv === undefined) delete process.env.GSD_RUNTIME;
|
|
else process.env.GSD_RUNTIME = prevEnv;
|
|
});
|
|
|
|
it('passes for runtime: claude (config)', () => {
|
|
expect(() => assertRuntimeSupportsAutoMode({ runtime: 'claude' })).not.toThrow();
|
|
});
|
|
|
|
it('passes when no runtime configured (defaults to claude)', () => {
|
|
expect(() => assertRuntimeSupportsAutoMode(undefined)).not.toThrow();
|
|
expect(() => assertRuntimeSupportsAutoMode({})).not.toThrow();
|
|
});
|
|
|
|
it('throws for runtime: codex via config', () => {
|
|
expect(() => assertRuntimeSupportsAutoMode({ runtime: 'codex' })).toThrow(/codex/);
|
|
});
|
|
|
|
it('throws for GSD_RUNTIME=codex even when config says claude', () => {
|
|
process.env.GSD_RUNTIME = 'codex';
|
|
expect(() => assertRuntimeSupportsAutoMode({ runtime: 'claude' })).toThrow(/codex/);
|
|
});
|
|
|
|
it('error message references issue #2832 and slash-command workaround', () => {
|
|
let caught: Error | undefined;
|
|
try {
|
|
assertRuntimeSupportsAutoMode({ runtime: 'codex' });
|
|
} catch (err) {
|
|
caught = err as Error;
|
|
}
|
|
expect(caught).toBeDefined();
|
|
expect(caught!.message).toMatch(/#2832/);
|
|
expect(caught!.message).toMatch(/gsd-discuss-phase|gsd-plan-phase|gsd-execute-phase/);
|
|
});
|
|
|
|
it('throws for gemini runtime', () => {
|
|
expect(() => assertRuntimeSupportsAutoMode({ runtime: 'gemini' })).toThrow();
|
|
});
|
|
|
|
it('throws for opencode runtime', () => {
|
|
expect(() => assertRuntimeSupportsAutoMode({ runtime: 'opencode' })).toThrow();
|
|
});
|
|
|
|
it('passes for unknown runtime values (fall through to claude default)', () => {
|
|
// Mirrors detectRuntime: unknown values are NOT in SUPPORTED_RUNTIMES,
|
|
// so they fall through to 'claude' rather than hard-blocking.
|
|
expect(() => assertRuntimeSupportsAutoMode({ runtime: 'totally-bogus' })).not.toThrow();
|
|
});
|
|
|
|
it('attributes source to config when GSD_RUNTIME is set to an unsupported value', () => {
|
|
// Unsupported env values fall through to config in detectRuntime; the
|
|
// gate's error message must report config (not the discarded env value)
|
|
// as the source so users debug the right thing.
|
|
process.env.GSD_RUNTIME = 'unsupported-env';
|
|
let caught: Error | undefined;
|
|
try {
|
|
assertRuntimeSupportsAutoMode({ runtime: 'codex' });
|
|
} catch (err) {
|
|
caught = err as Error;
|
|
}
|
|
expect(caught).toBeDefined();
|
|
expect(caught!.message).toMatch(/config\.runtime="codex"/);
|
|
expect(caught!.message).not.toMatch(/GSD_RUNTIME=unsupported-env/);
|
|
});
|
|
});
|