Summary
onSessionStart (and other hooks that fire from the CLI subprocess) execute relative to the bridge process CWD, not the per-session workingDirectory passed to createSession.
Root Cause
CopilotBridge constructs CopilotClient once at startup with no cwd option:
// src/core/bridge.ts
this.client = new CopilotClient({
autoStart: true,
telemetry: options?.telemetry,
env: options?.env,
// no cwd: option
});
The SDK defaults cwd to process.cwd() (the bridge's launch directory):
// @github/copilot-sdk dist/client.js
cwd: options.cwd ?? process.cwd(),
The CLI subprocess is spawned once from that fixed CWD. workingDirectory is passed as a per-session RPC config, but the CLI process itself was already started from the bridge's launch directory.
Impact
Hook scripts are loaded correctly by the bridge's Node-side loadHooks(workingDirectory) (for preToolUse etc. which the bridge calls directly). But sessionStart/sessionEnd hooks are fired by the CLI subprocess via hooks.invoke RPC - that subprocess resolves hook paths relative to its own CWD, not the session's workingDirectory.
Result: workspace-scoped sessionStart hooks silently do nothing for sessions whose workingDirectory differs from the bridge's launch directory.
Affected hooks
sessionStart
sessionEnd
errorOccurred
Hooks dispatched directly by the bridge Node process (preToolUse, postToolUse, userPromptSubmitted) are unaffected because the bridge loads those scripts explicitly from workingDirectory before passing them to the SDK.
Expected behavior
The CLI subprocess should be spawned (or re-spawned) with the session's workingDirectory as its CWD, OR the SDK should support passing a per-session workingDirectory override for hook resolution.
Steps to reproduce
- Start the bridge from directory A
- Create a session with
workingDirectory set to directory B
- Place a
sessionStart hook in B/.github/hooks/hooks.json
- Hook does not fire
Summary
onSessionStart(and other hooks that fire from the CLI subprocess) execute relative to the bridge process CWD, not the per-sessionworkingDirectorypassed tocreateSession.Root Cause
CopilotBridgeconstructsCopilotClientonce at startup with nocwdoption:The SDK defaults
cwdtoprocess.cwd()(the bridge's launch directory):The CLI subprocess is spawned once from that fixed CWD.
workingDirectoryis passed as a per-session RPC config, but the CLI process itself was already started from the bridge's launch directory.Impact
Hook scripts are loaded correctly by the bridge's Node-side
loadHooks(workingDirectory)(forpreToolUseetc. which the bridge calls directly). ButsessionStart/sessionEndhooks are fired by the CLI subprocess viahooks.invokeRPC - that subprocess resolves hook paths relative to its own CWD, not the session'sworkingDirectory.Result: workspace-scoped
sessionStarthooks silently do nothing for sessions whoseworkingDirectorydiffers from the bridge's launch directory.Affected hooks
sessionStartsessionEnderrorOccurredHooks dispatched directly by the bridge Node process (
preToolUse,postToolUse,userPromptSubmitted) are unaffected because the bridge loads those scripts explicitly fromworkingDirectorybefore passing them to the SDK.Expected behavior
The CLI subprocess should be spawned (or re-spawned) with the session's
workingDirectoryas its CWD, OR the SDK should support passing a per-sessionworkingDirectoryoverride for hook resolution.Steps to reproduce
workingDirectoryset to directory BsessionStarthook inB/.github/hooks/hooks.json