{"record":{"id":"aa511867e5930c00","repo":"github/copilot-sdk","slug":"copilotclient-is-in-mode-copilotclientmode-empty","errorCode":null,"errorMessage":"CopilotClient is in Mode = CopilotClientMode.Empty but the session config did not specify AvailableTools. Empty mode requires every session to explicitly opt into the tools it wants — e.g. `AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated)`.","messagePattern":"CopilotClient is in Mode = CopilotClientMode\\.Empty but the session config did not specify AvailableTools\\. Empty mode requires every session to explicitly opt into the tools it wants — e\\.g\\. `AvailableTools = new ToolSet\\(\\)\\.AddBuiltIn\\(BuiltInTools\\.Isolated\\)`\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"dotnet/src/Client.cs","lineNumber":973,"sourceCode":"            }\n        }\n    }\n\n    /// <summary>\n    /// Resolves <see cref=\"SessionConfigBase.AvailableTools\"/> /\n    /// <see cref=\"SessionConfigBase.ExcludedTools\"/> for the wire payload,\n    /// validating empty-mode requirements. <c>toolFilterPrecedence</c> is\n    /// always <c>excluded</c> so SDK consumers get composable allowlist /\n    /// denylist semantics.\n    /// </summary>\n    private (IList<string>? AvailableTools, IList<string>? ExcludedTools, OptionsUpdateToolFilterPrecedence ToolFilterPrecedence) ResolveToolFilterOptions(SessionConfigBase config)\n    {\n        ValidateToolFilterList(nameof(SessionConfigBase.AvailableTools), config.AvailableTools);\n        ValidateToolFilterList(nameof(SessionConfigBase.ExcludedTools), config.ExcludedTools);\n\n        if (_options.Mode == CopilotClientMode.Empty && config.AvailableTools is null)\n        {\n            throw new ArgumentException(\n                \"CopilotClient is in Mode = CopilotClientMode.Empty but the session config did \" +\n                \"not specify AvailableTools. Empty mode requires every session to explicitly \" +\n                \"opt into the tools it wants — e.g. \" +\n                \"`AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated)`.\",\n                nameof(config));\n        }\n\n        return (config.AvailableTools, config.ExcludedTools, OptionsUpdateToolFilterPrecedence.Excluded);\n    }\n\n    /// <summary>\n    /// Applies mode-specific defaults to a session config in place. Caller\n    /// values win — only fields left unset by the caller are filled in.\n    /// </summary>\n    private void ApplyConfigDefaultsForMode(SessionConfigBase config)\n    {\n        if (_options.Mode == CopilotClientMode.Empty)\n        {","sourceCodeStart":955,"sourceCodeEnd":991,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/dotnet/src/Client.cs#L955-L991","documentation":"The CopilotClient constructor throws this ArgumentException when the client is in CopilotClientMode.Empty and a session config does not specify AvailableTools. Empty mode intentionally exposes zero tools by default; every session must explicitly opt in to the tools it wants, so a config without AvailableTools is rejected.","triggerScenarios":"new CopilotClient(options with Mode = CopilotClientMode.Empty, ...) then calling CreateSessionAsync with a config whose AvailableTools is null. ValidateToolFilterList/Empty-mode check runs per session config.","commonSituations":"Switching the client to Empty mode for least-privilege but forgetting to update existing session configs; shared/default session configs written before the mode change; tests that create bare SessionConfig objects.","solutions":["Set AvailableTools on the session config, e.g. AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated).","Use a different CopilotClientMode if you don't need the strict empty-mode behavior.","Update all shared/default session config factories used with the Empty-mode client."],"exampleFix":"// before\nvar config = new SessionConfig(); // no AvailableTools\nvar session = await client.CreateSessionAsync(config); // throws in Empty mode\n// after\nvar config = new SessionConfig { AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated) };\nvar session = await client.CreateSessionAsync(config);","handlingStrategy":"validation","validationCode":"if (mode == CopilotClientMode.Empty && sessionConfig.AvailableTools is null)\n    throw new ArgumentException(\"Empty mode requires AvailableTools on every session config.\");","typeGuard":"static bool IsValidForEmptyMode(CopilotClientMode mode, SessionConfigBase cfg) => mode != CopilotClientMode.Empty || cfg.AvailableTools is not null;","tryCatchPattern":"try { session = await client.CreateSessionAsync(config); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"CopilotClientMode.Empty\")) { config.AvailableTools ??= new ToolSet().AddBuiltIn(BuiltInTools.Isolated); session = await client.CreateSessionAsync(config); }","preventionTips":["Always set AvailableTools in shared session config factories when using Empty mode","Encode the empty-mode requirement in your config builder API","Add a test that creates a session with every shipped config template"],"tags":["dotnet","configuration","tools","session","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}