Skip to content

feat: llmGateway.whenProxyUnavailable, fail closed by default - #12

Closed
LutzLange wants to merge 1 commit into
copilot/pr6-copilot-guidefrom
copilot/pr7-proxy-unavailable
Closed

LutzLange wants to merge 1 commit into
copilot/pr6-copilot-guidefrom
copilot/pr7-proxy-unavailable

Conversation

@LutzLange

Copy link
Copy Markdown
Owner

Stacked on this fork so the diff is this PR only (base: copilot/pr6-copilot-guide). A follow-up to the series; it goes to James with the next integration update.

Why. While the daemon runs without its loopback proxy (daemon.llmProxy.listen unset, or the address could not be bound), the programs behind it removed their managed entries. For VS Code on GitHub's models that removes the CAPI override, so Copilot Chat talks to GitHub directly, past the gateway. A company that routes Copilot through the gateway for identity, logging or guardrails may not want that silent fallback; another may prefer Copilot to keep working.

What it does.

  • llmGateway.whenProxyUnavailable (controller-deliverable): failClosed, the default, or failOpen. It covers programs.copilot and programs.vscode, in both modes.
  • failClosed. The managed files and sidecars are left as they are, with no read, write or new file. The tools stay pointed at the loopback port and fail. The program is inactive with a reason that names the policy.
  • failOpen. The entries are removed, as before.
  • Unchanged. Program removal, a mode switch, useLlmGateway: false, no gateway and no proxyUrl remove as before, whatever the policy. The next apply with the proxy writes the current entries. A stopped daemon changes no file, so the tools fail until it runs again.
  • Caveat. If another process holds the address, it keeps receiving those requests under the default, with VS Code's Copilot tokens and the pairing value. The README says to use the proxy on single-user devices, as before.
  • Rollout. LlmGatewayConfig denies unknown fields, so older controllers and daemons reject a configuration that carries the key. Set it only after both are upgraded; the default needs no key.
  • Docs. README "Without the proxy", the program sections, the status table and the dry-run note; the example guide's step 5 note and step 6 "Proxy gone". Schema regenerated. It also formats one line PR 5 left unformatted, which made the base fail cargo fmt --check.

Tests. Agent lib 297 to 302. The three proxy-absent removal tests now set failOpen. New failClosed tests cover the Copilot CLI, chat language models and settings.json planners: bytes and sidecar kept, the inactive reason, the proxy back on another port, program removal still removes, no file created. Config default, parse and serialize are covered too. Gate: fmt, clippy, agent 302, core 22, schema. VS Code and Copilot Docker tests: 6 of 6.

Review. One review (Opus) found no blocker and no bypass. It raised a wording major (the port may belong to another process) and three stale doc sentences; all are fixed in this commit.

Lab (Linux VM, user mode, controller-pushed Copilot CLI and VS Code on GitHub's models). Run with lab/checks/pr7-policy.sh: 11 of 11.

  • Without the proxy, under the default, settings.json and providers.json keep their bytes, and both programs are inactive with the fail-closed reason.
  • VS Code, relaunched, answers a chat prompt with "Language model unavailable" in 2 s, with no fallback to GitHub.
  • Pushing failOpen removes the override and the providers.
  • With the proxy back, both return.

Not covered. macOS and Windows.

🤖 Generated with Claude Code

While the daemon runs without its loopback proxy, the programs behind it
used to remove their managed entries. For VS Code on GitHub's models that
removes the CAPI override, so Copilot Chat talks to GitHub directly, past
the gateway.

llmGateway.whenProxyUnavailable now decides, for programs.copilot and
programs.vscode (both chatLanguageModels.json and the settings.json
override):
- failClosed (default): the managed entries are left as they are, so the
  tools stay pointed at the closed loopback port and fail; the program is
  inactive with FAIL_CLOSED_REASON.
- failOpen: the entries are removed as before (PROXY_ABSENT_REASON).

Program removal, ownModels/githubModels switches, useLlmGateway: false and
a missing proxyUrl remove as before. The next apply with the proxy writes
the current entries. Older daemons and controllers reject the key
(deny_unknown_fields), so it is set only after both are upgraded; the
default needs no key. If the address could not be bound because another
process holds it, that process keeps receiving the requests under
failClosed; the README says so (single-user devices, as for the proxy).

Also formats PROXY_ABSENT_REASON (left unformatted on the base, so the
base fails cargo fmt --check).

Tests (agent lib 297 -> 302): the three proxy-absent removal tests now set failOpen; new tests
for failClosed in the Copilot CLI, chat language models and settings.json
planners (bytes and sidecar kept, inactive reason, proxy back on another
port, program removal still removes, no file created), and the config
default/parse/serialize (5 new).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LutzLange

Copy link
Copy Markdown
Owner Author

Superseded: landed upstream in agentdesktop-dev#146 (llmGateway.whenProxyUnavailable).

@LutzLange LutzLange closed this Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant