Skip to content

Commit aedf2c3

Browse files
Address PR review comments
- nodejs/dotnet/go: best-effort disconnect/destroy session when the post-create options.update RPC fails so empty mode never leaves a runtime session alive with permissive defaults. Logic lives inside updateSessionOptionsForMode so call sites stay one line. - python: same cleanup already happens; harden it so a failing disconnect doesn't mask the original error. - python _normalize_tool_filter: reject bare str so passing 'builtin:bash' fails fast instead of silently splitting into chars. - nodejs client.ts: fix doc comment claiming append-mode is rejected in empty mode (we promote it to customize). - go toolset.go: drop misleading 'implicit conversion' wording. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4776302 commit aedf2c3

7 files changed

Lines changed: 77 additions & 30 deletions

File tree

dotnet/src/Client.cs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -677,15 +677,34 @@ private async Task UpdateSessionOptionsForModeAsync(CopilotSession session, Sess
677677

678678
if (!hasAnyPatch) return;
679679

680+
try
681+
{
680682
#pragma warning disable GHCP001
681-
await session.Rpc.Options.UpdateAsync(
682-
skipCustomInstructions: skipCustomInstructions,
683-
customAgentsLocalOnly: customAgentsLocalOnly,
684-
coauthorEnabled: coauthorEnabled,
685-
manageScheduleEnabled: manageScheduleEnabled,
686-
installedPlugins: installedPlugins,
687-
cancellationToken: cancellationToken).ConfigureAwait(false);
683+
await session.Rpc.Options.UpdateAsync(
684+
skipCustomInstructions: skipCustomInstructions,
685+
customAgentsLocalOnly: customAgentsLocalOnly,
686+
coauthorEnabled: coauthorEnabled,
687+
manageScheduleEnabled: manageScheduleEnabled,
688+
installedPlugins: installedPlugins,
689+
cancellationToken: cancellationToken).ConfigureAwait(false);
688690
#pragma warning restore GHCP001
691+
}
692+
catch
693+
{
694+
// The runtime session exists but the post-create options
695+
// patch failed — best-effort destroy so we don't leak it
696+
// (in empty mode it would otherwise stay alive with
697+
// permissive defaults).
698+
try
699+
{
700+
await session.DisposeAsync().ConfigureAwait(false);
701+
}
702+
catch
703+
{
704+
// Swallow: original error is what the caller needs.
705+
}
706+
throw;
707+
}
689708
}
690709

691710
/// <summary>

go/client.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -777,14 +777,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
777777
CoauthorEnabled: config.CoauthorEnabled,
778778
ManageScheduleEnabled: config.ManageScheduleEnabled,
779779
}); err != nil {
780-
// In empty mode, refuse to expose a session whose safe-defaults
781-
// patch was rejected: tear it down so callers never get a
782-
// permissive session.
783-
_ = session.Disconnect()
784-
c.sessionsMux.Lock()
785-
delete(c.sessions, sessionID)
786-
c.sessionsMux.Unlock()
787-
return nil, fmt.Errorf("failed to apply mode-specific session options: %w", err)
780+
return nil, err
788781
}
789782

790783
return session, nil
@@ -1002,11 +995,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
1002995
CoauthorEnabled: config.CoauthorEnabled,
1003996
ManageScheduleEnabled: config.ManageScheduleEnabled,
1004997
}); err != nil {
1005-
_ = session.Disconnect()
1006-
c.sessionsMux.Lock()
1007-
delete(c.sessions, sessionID)
1008-
c.sessionsMux.Unlock()
1009-
return nil, fmt.Errorf("failed to apply mode-specific session options: %w", err)
998+
return nil, err
1010999
}
10111000

10121001
return session, nil

go/mode_empty.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,17 @@ func (c *Client) updateSessionOptionsForMode(ctx context.Context, session *Sessi
193193
if !hasAny {
194194
return nil
195195
}
196-
_, err := session.RPC.Options.Update(ctx, patch)
197-
return err
196+
if _, err := session.RPC.Options.Update(ctx, patch); err != nil {
197+
// The runtime session exists but the post-create options patch
198+
// failed — best-effort disconnect so we don't leak it (in empty
199+
// mode it would otherwise keep running with permissive defaults).
200+
_ = session.Disconnect()
201+
c.sessionsMux.Lock()
202+
delete(c.sessions, session.SessionID)
203+
c.sessionsMux.Unlock()
204+
return fmt.Errorf("failed to apply mode-specific session options: %w", err)
205+
}
206+
return nil
198207
}
199208

200209
// optBackInFields is the subset of SessionConfig / ResumeSessionConfig shared

go/toolset.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ const (
3939
// built-in, even if an MCP server or custom-agent extension happens to
4040
// register a tool with the same wire name.
4141
//
42-
// ToolSet's zero value is ready to use. ToolSet implements implicit conversion
43-
// to []string via [ToolSet.ToSlice], and the [SessionConfig] fields accept
44-
// []string directly; pass tools as `(&ToolSet{}).AddBuiltIn(...).ToSlice()`.
42+
// ToolSet's zero value is ready to use. Convert to []string via [ToolSet.ToSlice]
43+
// before passing to [SessionConfig] fields, e.g.
44+
// `(&ToolSet{}).AddBuiltIn(...).ToSlice()`.
4545
type ToolSet struct {
4646
items []string
4747
}

nodejs/src/client.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -899,8 +899,11 @@ export class CopilotClient {
899899
/**
900900
* Returns the systemMessage config to use, adjusted for the current mode.
901901
* In empty mode we ensure the environment_context section is removed
902-
* unless the app has already taken control of it; append mode is rejected
903-
* because it would leave environment info in the prompt.
902+
* unless the app has already taken control of it. `append` (and
903+
* unspecified) mode is promoted to `customize` so we can also strip
904+
* environment_context; the caller's `content` is preserved verbatim
905+
* because the runtime appends it as additional instructions in both
906+
* customize and append modes.
904907
*/
905908
private getSystemMessageConfigForMode(
906909
supplied: SystemMessageConfig | undefined
@@ -966,8 +969,22 @@ export class CopilotClient {
966969
if (config.manageScheduleEnabled !== undefined)
967970
patch.manageScheduleEnabled = config.manageScheduleEnabled;
968971
}
969-
if (Object.keys(patch).length > 0) {
972+
if (Object.keys(patch).length === 0) {
973+
return;
974+
}
975+
try {
970976
await session.rpc.options.update(patch);
977+
} catch (e) {
978+
// The runtime session exists but the post-create options
979+
// patch failed — best-effort disconnect so we don't leak
980+
// it (in empty mode it would otherwise keep running with
981+
// permissive defaults).
982+
try {
983+
await session.disconnect();
984+
} catch {
985+
// Swallow: original error is the one the caller needs.
986+
}
987+
throw e;
971988
}
972989
}
973990

python/copilot/_mode.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,21 @@ def __len__(self) -> int:
105105

106106

107107
def _normalize_tool_filter(value: Any) -> list[str] | None:
108-
"""Accept ``ToolSet``, ``list[str]``, or ``None``; return a list or ``None``."""
108+
"""Accept ``ToolSet``, ``list[str]``, or ``None``; return a list or ``None``.
109+
110+
Reject plain ``str`` explicitly — ``list("foo")`` would silently shred it
111+
into characters, sending an invalid tool filter list on the wire.
112+
"""
109113
if value is None:
110114
return None
111115
if isinstance(value, ToolSet):
112116
return value.to_list()
117+
if isinstance(value, str):
118+
raise TypeError(
119+
"tool filter must be a ToolSet or list[str], not str. "
120+
'Pass a single-element list (e.g. ["builtin:bash"]) or a '
121+
"ToolSet (e.g. ToolSet().add_builtin('bash'))."
122+
)
113123
return list(value)
114124

115125

python/copilot/client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3283,7 +3283,10 @@ async def _apply_post_create_options_patch(
32833283
except BaseException:
32843284
with self._sessions_lock:
32853285
self._sessions.pop(session.session_id, None)
3286-
await session.disconnect()
3286+
try:
3287+
await session.disconnect()
3288+
except BaseException:
3289+
pass
32873290
raise
32883291

32893292
async def _set_session_fs_provider(self) -> None:

0 commit comments

Comments
 (0)