-
Notifications
You must be signed in to change notification settings - Fork 424
fix: isolate copilot_sdk_driver test session state writes to prevent false-positive tool-denial issues #39940
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c8f4e3d
a1bd2e4
d9ee22e
9ae9bfb
d64b8bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,10 +82,11 @@ function extractPromptFromArgs(args) { | |
| * RuntimeConnection: typeof import("@github/copilot-sdk").RuntimeConnection, | ||
| * approveAll: typeof import("@github/copilot-sdk").approveAll | ||
| * }, | ||
| * sessionStateBaseDir?: string, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] 💡 Options to consider
it('respects explicit sessionStateBaseDir over env var', async () => {
const explicitDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sdk-explicit-'));
try {
await runWithCopilotSDK({ ..., sessionStateBaseDir: explicitDir });
// assert files written to explicitDir, not testSessionStateDir
} finally {
fs.rmSync(explicitDir, { recursive: true, force: true });
}
});Either way, the current state leaves the parameter untested and unused in production. |
||
| * }} options | ||
| * @returns {Promise<{exitCode: number, output: string, hasOutput: boolean, durationMs: number}>} | ||
| */ | ||
| async function runWithCopilotSDK({ sdkUri, prompt, logger, attempt = 0, model, connectionToken, provider, maxToolDenials, permissionConfig, coreLogger, sdkModule }) { | ||
| async function runWithCopilotSDK({ sdkUri, prompt, logger, attempt = 0, model, connectionToken, provider, maxToolDenials, permissionConfig, coreLogger, sdkModule, sessionStateBaseDir }) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
💡 DetailThe parameter was introduced specifically to give callers a clean injection point, but
The test should pass |
||
| // Lazy-require to avoid loading the SDK when it is not needed. | ||
| // The SDK is large and has side-effects on import (worker threads, etc.). | ||
| const { CopilotClient, RuntimeConnection, approveAll } = sdkModule ?? require("@github/copilot-sdk"); | ||
|
|
@@ -106,7 +107,9 @@ async function runWithCopilotSDK({ sdkUri, prompt, logger, attempt = 0, model, c | |
|
|
||
| // Session state directory — mirrors the target path used by unified_timeline.cjs. | ||
| // /tmp/gh-aw/sandbox/agent/logs/copilot-session-state/{sessionId}/events.jsonl | ||
| const sessionStateBase = path.join(os.tmpdir(), "gh-aw", "sandbox", "agent", "logs", "copilot-session-state"); | ||
| // GH_AW_SESSION_STATE_BASE_DIR may be set in tests to redirect writes to an isolated directory. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnose] The comment frames 💡 Suggested rewording// Override session-state base directory (e.g. for test isolation or CI sandboxing).
const sessionStateBase = sessionStateBaseDir ?? process.env.GH_AW_SESSION_STATE_BASE_DIR ?? defaultSessionStateBase;Coupling the comment to "tests" may mislead future readers who want to use it in other contexts. |
||
| const defaultSessionStateBase = path.join(os.tmpdir(), "gh-aw", "sandbox", "agent", "logs", "copilot-session-state"); | ||
| const sessionStateBase = sessionStateBaseDir ?? process.env.GH_AW_SESSION_STATE_BASE_DIR ?? defaultSessionStateBase; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Production code now has an undocumented env-var override that can silently lose all audit events: 💡 Detail and suggested fixIf this env var is set in any real agent environment — by accident, CI variable leakage, or a misconfigured workflow — all session-state JSONL writes are silently redirected to an arbitrary path. The cleaner design is to keep the env-var lookup only in tests, pass the override via the // Production code — no env var
const sessionStateBase = sessionStateBaseDir ?? defaultSessionStateBase;In the test harness, pass the dir explicitly: await runWithCopilotSDK({ ..., sessionStateBaseDir: testSessionStateDir });If keeping the env var, at minimum emit a visible log line when it overrides the default: if (process.env.GH_AW_SESSION_STATE_BASE_DIR && !sessionStateBaseDir) {
logger?.warn?.(`[session] GH_AW_SESSION_STATE_BASE_DIR overrides default session state path`);
} |
||
|
|
||
| /** @type {ReadonlyArray<NonNullable<import("@github/copilot-sdk").CopilotClientOptions["logLevel"]>>} */ | ||
| const VALID_LOG_LEVELS = ["none", "error", "warning", "info", "debug", "all"]; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/diagnose] The
beforeAll/afterAllhooks correctly prevent test-generated events from polluting the production session-state path, but there's no assertion confirming the redirect actually works. Without a self-verifying test, a future refactor that breaks the isolation could go undetected untilhandle_agent_failure.cjsfires again.💡 Consider a smoke-test assertion
After a
runWithCopilotSDKcall in an existing test, you could assert:This turns the fix into a regression guard, not just an isolation helper.