Skip to content

fix(ipc): find KiCad's Windows endpoint in the pipe namespace - #530

Merged
neusse merged 1 commit into
mainfrom
fix/529-windows-pipe-detection
Sep 13, 2026
Merged

neusse merged 1 commit into
mainfrom
fix/529-windows-pipe-detection

Conversation

@mixelpixx

Copy link
Copy Markdown
Owner

Summary

NNG maps ipc:// to a named pipe on Windows, and a named pipe has no filesystem presence. Discovery probed the candidate path as a file, so is_adoptable answered "no" for every Windows install and detect_ipc_address could never succeed there.

The consequence was not a missing convenience. A Konnect launched by an MCP client — the shipped PCM package registered with a client, carrying no ipc_address and no KICAD_API_SOCKET — reported the transport unreachable with KiCad running and the API server enabled, so every hybrid tool took its direct-file fallback while KiCad held the board open. That is the one outcome attempt_ipc_write's gate exists to prevent, reached because discovery could not see a transport that was in fact reachable. Live-only tools refused outright.

Closes #529

Approach

The module already said what the fix was — "Inspecting the pipe … is the real answer and is left for a change that can be tested on Windows" — so this is that change, made on a Windows machine with KiCad 10.0.5.

  • Look where the endpoint is. Windows resolves the candidate against the pipe namespace instead of the filesystem. FindFirstFileW over \\.\pipe lists bound names without opening any of them, so the metadata-only rule IPC socket auto-detection wedges KiCad's API server on Linux (no reply to any client until restart) #498 made non-negotiable still holds. std::fs::metadata would not have done: on Windows it opens a handle, and against KiCad's endpoint that consumes a server instance.
  • No new candidates and no new dependencies. file_name() returns the bound name verbatim — drive letter and backslashes intact — which is exactly the candidate path, so the existing candidate list and the format_address output are unchanged. std::fs::read_dir does the enumeration; no windows-sys/winapi is added.
  • Case-insensitive matching, because Windows paths are and the bound name is KiCad's spelling of the temp directory rather than ours.
  • Unix is untouched, and a third arm keeps any other target compiling and adopting nothing.

The limit, stated rather than implied

The pipe namespace is machine-global and any local account may bind a name in it, so a squatter could be adopted. Unix answers that with an ownership check; the Windows equivalent needs the pipe's security descriptor, which means opening it. An explicitly configured ipc_address has always carried the same exposure, so the doc comment records this as the state of the art on the platform rather than implying a check that is not there. Happy to split it out as a follow-up if you want it pursued.

Branch and dependencies

Base branch: main at f0f5ad0.
Depends on: nothing. Overlap: none — crates/konnect-ipc/src/socket.rs plus three docs; no open PR touches them.
Series order: none — single PR, one commit.
Next PR to promote after this one: none in this chain.

Compatibility and safety

  • Behaviour change, Windows only: tools that reported source: "file" with fallback_reason.kind: transport_unreachable now report source: "ipc" when KiCad is running with the API server enabled, and their edits go through KiCad instead of to the saved file. Live-only tools start working. get_installation_info reports the discovered endpoint instead of a null one. Recorded in docs/API_MIGRATIONS.md.
  • No response field, argument, or tool changed shape. Nothing changes on Linux or macOS, or for any caller that set the address explicitly.
  • Discovery still runs only at startup, so a server launched before KiCad stays unresolved for its lifetime — unchanged, and docs/TROUBLESHOOTING.md still says so.
  • No tool added or removed; cargo xtask fix-doc-counts --check unchanged. Rollback: revert the one commit.

Validation

Run on commit f67fe06 (Windows 11, KiCad 10.0.5), exit codes captured directly:

cargo fmt --all -- --check                                        exit 0
cargo clippy --workspace --locked --all-targets -- -D warnings    exit 0
cargo test --workspace --locked --lib --tests                     exit 0 (1784 passed, 0 failed)
cargo test --workspace --locked --doc                             exit 0
cargo xtask fix-doc-counts --check                                unchanged
cargo test -p konnect-ipc --lib socket                            8 passed, 1 ignored (5 new)
  • cargo fmt --all -- --check
  • cargo test --workspace --locked --lib --tests
  • cargo test --workspace --locked --doc
  • cargo clippy --workspace --locked --all-targets -- -D warnings
  • Real-KiCad check: the #[ignore] live test and the end-to-end run below, both against KiCad 10.0.5 on this machine. The Unix branch is unchanged and CI covers its matrix; the new code is #[cfg(windows)], so the Windows leg of CI is what exercises it there.

Reproduced first, with the released v0.11.1 PCM binary, no config file and no KICAD_* variables, pcbnew open on a board:

get_installation_info -> ipc: { configured: false, endpoint: null }
run_drc               -> OK        (kicad-cli resolves fine)
open_project          -> ipc_available: false, kicad_ui_running: false, open_board_count: 0

And the mechanism, measured directly with pcbnew running:

socket FILE  %TEMP%\kicad\api.sock                      exists: False
named PIPE   \\.\pipe\%TEMP%\kicad\api.sock             exists: True

Negative controls (each neutered, then restored):

guard neutered tests that fail
Windows arm back to adopting nothing detection_finds_a_live_pipe_without_connecting_to_it
candidate probed as a file again (the original defect) detection_finds_a_live_pipe_without_connecting_to_it

Both neuters are caught by that one test, and deliberately so: it is the only one that goes through the production is_adoptable. The other four state what is bound rather than binding it, so they exercise is_adoptable_among directly and survive either neuter — which is what makes the live-pipe test load-bearing rather than decorative.

Live, on this branch — same binary invocation as the reproduction, still no config and no KICAD_*:

detect_ipc_address()  -> ipc://C:\Users\…\AppData\Local\Temp\kicad\api.sock
get_installation_info -> ipc: { configured: true, endpoint: ipc://…\kicad\api.sock }
open_project          -> ipc_available: true, kicad_ui_running: true, open_board_count: 1

Review checklist

  • The diff is focused and contains no generated output, personal data, or unrelated cleanup.
  • The branch includes current upstream/main, has no merge conflicts, and CI passed on this exact head.
  • The branch was based on latest upstream/main, not a release tag.
  • The PR shows only its unique commits and diff; dependencies and series position are explicit.
  • Every review conversation is resolved.
  • New names follow docs/NAMING_CONVENTIONS.md (PIPE_NAMESPACE, pipe_names, is_adoptable_among); no public renames.
  • New behavior and failure paths have regression coverage, including the no-connect property against a real pipe server.
  • No file mutations. No IPC mutations — discovery opens nothing.
  • No tools added/removed; cargo xtask fix-doc-counts --check unchanged.

Maintainer merge state

🤖 Generated with Claude Code

@mixelpixx

Copy link
Copy Markdown
Owner Author

Filed the documented limit as #531, with a measured self-test rather than a theoretical note.

Binding KiCad's default pipe name before KiCad starts does capture Konnect's connection — but the squatter speaks no NNG, so the bounded Ping fails and the session falls back exactly as designed. What protects us is the protocol handshake, not an ownership check. A peer that implemented NNG would get through, and separately KiCad silently loses its API server when the name is already taken.

The cheapest real improvement that came out of it is not a security check at all: Konnect can already distinguish "no pipe bound" from "a pipe answered and was not KiCad", and today both render as the transport being unreachable. That is on #531 as the recommended first step. Nothing in this PR changes the trust model — an explicitly configured address always carried the same exposure — so I do not think it blocks the merge.

@neusse neusse left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One release-classification correction is needed before this can enter the merge queue.

  • Change the new docs/API_MIGRATIONS.md heading from minor release to patch release. This PR fixes Windows endpoint discovery and changes no tool, argument, or response shape; GOVERNANCE.md classifies that as a patch.

Everything else on exact head f67fe0666b8f800d7c3901365dd8f39fc1811745 matches #529: it is based on current main, the Windows namespace probe does not connect to the endpoint, the live Windows evidence covers default discovery, all ten required checks pass, and Closes #529 is correct. Please make that focused wording correction and let CI rerun on the new head.

@neusse neusse added bug Something isn't working P1 High-value workflow reliability status:waiting-on-author Next actor: the PR author — one checklist, 14-day target and removed status:waiting-on-review Next actor: maintainer labels Sep 12, 2026
@mixelpixx
mixelpixx force-pushed the fix/529-windows-pipe-detection branch from f67fe06 to 709267f Compare September 13, 2026 01:10
@mixelpixx

Copy link
Copy Markdown
Owner Author

Corrected. The heading now reads (patch release), and that line is the whole diff from f67fe06. New exact head: 709267fc8eb4af4b05ef2d053a88d8bed96b2fd9, still one commit on f0f5ad0, all ten required checks green on it. Local doc guards (doc_tool_counts, asset_references, schema_migrations) and fix-doc-counts --check pass.

@mixelpixx mixelpixx added status:waiting-on-review Next actor: maintainer and removed status:waiting-on-author Next actor: the PR author — one checklist, 14-day target labels Sep 13, 2026

@neusse neusse left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The patch-release correction resolves the earlier requested change. Spec and standards review found no remaining implementation defect at exact head 709267fc8eb4af4b05ef2d053a88d8bed96b2fd9.

This approval is for the reviewed diff only; the PR is not merge-ready until current main is incorporated as noted below.

@neusse

neusse commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Please rebase this exact head onto current main (39e006131cb7d5ba3f1d3c9f07021a507a9f7370) and rerun CI. No code change is requested: the earlier patch-release correction is accepted, and the focused implementation review passes.

This remains first in the least-resistance train: #530 -> #528 -> #523 -> #535. Once the refreshed head is green, it needs only the final exact-head check before merge.

@neusse neusse added status:waiting-on-author Next actor: the PR author — one checklist, 14-day target and removed status:waiting-on-review Next actor: maintainer labels Sep 13, 2026
NNG maps `ipc://` to a named pipe on Windows, and that pipe has no
filesystem presence: with pcbnew running, `%TEMP%\kicad\api.sock` does not
exist while `\\.\pipe\%TEMP%\kicad\api.sock` does. Discovery probed the
candidate as a file, so `is_adoptable` answered "no" for every Windows
install and `detect_ipc_address` could never succeed there.

The consequence was not a missing convenience. A Konnect launched by an MCP
client — the shipped PCM package registered with a client, carrying no
`ipc_address` and no `KICAD_API_SOCKET` — reported the transport unreachable
with KiCad running and the API server enabled, so every hybrid tool took its
direct-file fallback while KiCad held the board open. That is the one
outcome `attempt_ipc_write`'s gate exists to prevent, reached because
discovery could not see a transport that was in fact reachable. Live-only
tools refused outright.

Windows now looks the same candidate up in the pipe namespace.
`FindFirstFileW` over `\\.\pipe` lists bound names without opening any of
them, so the metadata-only rule #498 made non-negotiable still holds —
`std::fs::metadata` would not have done, since on Windows it opens a handle
and against KiCad's endpoint that consumes a server instance. `file_name()`
returns the bound name verbatim, which is the candidate path, so the
candidate list is unchanged. Matching is case-insensitive because Windows
paths are.

The pipe namespace is machine-global and any local account can bind a name
in it, so a squatter could be adopted; the Windows answer to that needs the
pipe's security descriptor, which means opening it. An explicitly configured
address has always carried the same exposure, so the doc comment states the
limit rather than implying an ownership check that is not there.

The test that asserted Windows adopts nothing encoded the bug and is
replaced. New coverage: a bound name is adopted, an absent one and an
unreadable namespace are not, matching ignores case, and — against a real
`tokio` pipe server whose name embeds a path that does not exist on disk —
detection finds it and does not become its client, the Windows counterpart
of `detection_never_connects_to_a_candidate`. One `#[ignore]` live test
asserts the property the issue is about: with KiCad running, detection
succeeds with no configuration at all.

Verified live here (KiCad 10.0.5, Windows 11). Before: `open_project`
reported `ipc_available: false` with an empty endpoint. After, same binary
invocation with no config file and no `KICAD_*` variables: endpoint
discovered, `ipc_available: true`, `kicad_ui_running: true`, one open board.

Closes #529

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mixelpixx
mixelpixx force-pushed the fix/529-windows-pipe-detection branch from 709267f to 705a30d Compare September 13, 2026 15:11
@mixelpixx

Copy link
Copy Markdown
Owner Author

Rebased onto current main (39e006131cb7d5ba3f1d3c9f07021a507a9f7370). New exact head: 705a30d23a305031007dc278a1cb308aa760fbb4, one commit, no code change: git diff of the commit is byte-identical to 709267f's (verified with cmp on the two patches). The base moved only by #534's two documentation files, which this branch does not touch.

All ten required checks green on 705a30d. Local gate on the same head: fmt, clippy -D warnings, --lib --tests 1784 passed / 0 failed, --doc, fix-doc-counts --check unchanged.

Ready for the final exact-head check.

@mixelpixx mixelpixx added status:waiting-on-review Next actor: maintainer and removed status:waiting-on-author Next actor: the PR author — one checklist, 14-day target labels Sep 13, 2026
@neusse
neusse merged commit 6a0ec5e into main Sep 13, 2026
10 checks passed
@neusse
neusse deleted the fix/529-windows-pipe-detection branch September 13, 2026 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:platform Install, discovery, OS and KiCad-version compatibility bug Something isn't working P1 High-value workflow reliability status:waiting-on-review Next actor: maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: KiCad IPC auto-detection can never succeed (nng ipc:// is a named pipe, not a file), so a default install has no live IPC

2 participants