fix(ipc): report why KiCad did not answer a health check - #535
Conversation
|
Head |
neusse
left a comment
There was a problem hiding this comment.
Two public-contract corrections are needed before this can enter the merge queue.
- Correct
docs/API_MIGRATIONS.md: it currently says “No existing field changed value” and then documents thatopen_project.messagenow changes foraccess_denied,handshake_failed, andrequest_failed. Say instead that no existing field was removed or renamed, and explicitly record the changed message wording. Also describeipc_failure: nullas “no failure kind was established” rather than only “KiCad answered,” because the health-check timeout path also returns null. - Account for the exported Rust API change:
TransportUnreachablechanges from a constructible unit struct to a field struct containingreason. Either preserve the old constructible shape with a separate typed marker/context, or document this source-compatibility break explicitly in the migration entry.
On exact head 249cc89a32267c07d9f7671e71bcaa76a37131d7, the typed classification, handler behavior, platform tests, ten required checks, Closes #532, and Part of #531 accounting otherwise look sound. The reporter's exact cross-account Codex run remains useful validation debt, not a merge wall: the restricted-descriptor pipe provides an adequate deterministic proxy. Please make the focused compatibility/documentation correction and rerun CI.
|
Merge-train placement: please make the two requested compatibility/documentation corrections, but hold the final reconstruction and CI run until #523 lands. The shared-file order is #530 → #528 → #523 → #535, so each branch is rebuilt once rather than after every predecessor. #535 remains the terminal PR for #532 and partial work for #531. |
|
Both corrections are made and committed locally. Per the train, they're held until #523 lands, then pushed once with the reconstruction.
Local gate on the corrected branch: 1797 passed / 0 failed, fmt, clippy |
|
#523 has landed as Please reconstruct the corrected local implementation once onto current |
249cc89 to
d34bf44
Compare
|
Reconstructed onto current Corrections, as held since 01:17Z:
Reconstruction evidence. Per-file hunks are identical to the corrected pre-rebase tree ( All ten required checks green on PR body updated to the new head. Ready for the exact-head check. Terminal: |
neusse
left a comment
There was a problem hiding this comment.
One small public-contract wording correction remains on exact head d34bf442da3af9d56dbc68aaba23195d5b6656ef.
The implementation correctly returns ipc_failure.kind: "request_failed" when KiCad answers the Ping with an error status such as AS_NOT_READY. Several descriptions instead say ipc_failure is null when “KiCad answered,” which contradicts that behavior. Please replace that wording with “the Ping succeeded with AS_OK” (or equivalent), while retaining the other null case: check_kicad_ui's outer deadline expired before an outcome was established.
Please correct the wording consistently in:
crates/konnect-core/src/tools/mod.rs(ipc_failure_evidencedocumentation)crates/konnect-core/src/tools/verification.rs(check_kicad_uitool description)crates/konnect-ipc/src/client.rs(failure_kindandfailure_messagedocumentation)docs/API_MIGRATIONS.mddocs/TROUBLESHOOTING.md- the PR description
Everything else at this head passes review: current main is incorporated, all ten required checks are green, the public TransportUnreachable unit-struct shape is preserved, tests and platform coverage are sound, and Closes #532 / Part of #531 is correct. This is a focused wording fix; no runtime redesign is requested.
d34bf44 to
938c3f6
Compare
2dcb64b to
104c5aa
Compare
|
All four corrected on the new exact head
All ten required checks green on PR body updated (evidence, the new headline wording, the served-dispatch tests). Ready for the exact-head check. Terminal: |
|
Please reconstruct this once onto current The substantive review is now complete on The remaining blocker is branch freshness. This head's actual parent/merge-base is
Once that refreshed exact head is green, this is next for final review in the merge train. |
104c5aa to
e407850
Compare
|
Reconstructed once onto current All ten required checks green on Nothing changed but the base. Ready for the final exact-head check. Terminal: |
e407850 to
d9431df
Compare
|
All ten required checks green on Nothing changed but the base. Terminal: |
|
The substantive review is complete on d9431df: independent spec and standards reviews found no material blockers, its parent is current main 9ca98f1, and all ten required checks pass. The stable patch matches the previously accepted e407850 content. Closes #532 / Part of #531 accounting is correct. One queue update: #579 now has its narrow recovery correction and is awaiting George exact-head approval. A merge simulation of the two heads finds one overlap in crates/konnect-core/src/mcp/handler.rs, where both PRs append independent test modules. We will land #579 first; then reconstruct #535 once onto resulting main, preserving BOTH annotation-dispatch and IPC-failure-dispatch test modules. No production redesign is requested. Migration entries merge automatically in this simulation; still verify all existing entries are retained. Please hold final reconstruction/CI until #579 lands, then update the stale body head/base and historical #523/#528/#530 overlap section, rerun required checks, and reply with the new full head SHA. That refreshed head gets the final merge approval request. The exact reporter cross-account observation remains disclosed validation debt; #531 peer-identity work is not claimed complete. |
|
The dependency has landed: #579 merged into main at 99da7d7. #535 is next in the least-resistance order. Please refresh against this latest main once, preserving both the annotation served-dispatch tests and your IPC-failure dispatch tests in mcp/handler.rs. Update the stale base/head and dependency notes in the PR body, then rerun required CI on the refreshed head. Our review of d9431df found no material code blockers; this is integration and final-head validation, not a request for redesign. Keep Closes #532 and Part of #531 intact. |
check_kicad_ui and open_project reduced every IPC failure to one false. No configured address, nothing listening, a listener that refused this account, and a listener that is not KiCad all looked like KiCad being closed. #532's reporter had KiCad listening and a Konnect running as the Codex sandbox account, and nothing Konnect said could tell them so. send_command dials synchronously, so NNG already returns a distinct error for each case; ping() discarded it. Measured on Windows against named pipes with Konnect's own socket options: no pipe gives ConnectionRefused, a pipe granting this account only read access gives PermissionDenied before the listener sees a client, a listener that closes gives Closed, and a silent one gives TimedOut after NNG's fixed 10-second negotiation limit. NNG's POSIX dialer maps ENOENT to ECONNREFUSED and EACCES to EPERM, so Unix sockets land on the same variants. The reason is classified from that error into a typed UnreachableReason and carried in a private context layer directly above the TransportUnreachable marker, whose Display is the message, so error chains render byte-for-byte as before. TransportUnreachable itself stays a constructible unit struct: code that builds one keeps compiling and keeps classifying as unreachable, reporting transport_error. unreachable_reason() reads the layer with anyhow's downcast_ref, which searches every context level. ping_outcome() keeps a request that did not complete and may have reached the endpoint (an explicit KiCad status such as AS_NOT_READY proves receipt; a timeout or malformed reply does not) apart from one that never arrived; ping() keeps its signature. Both tools report ipc_failure {kind, message}. It is null when no failure kind was established: the Ping succeeded with AS_OK, or check_kicad_ui's own timeout expired first. open_project's headline follows the kind; a mock answering AS_NOT_READY pins that wiring, which the first neuter pass found untested. The listeners in the new tests are real pipes and sockets. The access case builds a pipe whose security descriptor leaves this account only Everyone's read access, the position a different user is in; the kind proves the refusal, and the docs name a different account or a restrictive ACL as likely causes rather than established ownership. Both tools' responses are also proven through the served tools/call path, in-process and over the real stdio protocol. The migration entry records the changed open_project wording verbatim and defines null; TROUBLESHOOTING.md gains the per-kind fixes and the out-of-sandbox HTTP setup from #300. Closes #532 Part of #531 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
d9431df to
8a50244
Compare
|
Maintainer refresh complete: new exact head 8a50244 on current main 71d9e0c. Resolved only the handler.rs append overlap, preserving both annotate_dispatch_tests and ipc_failure_dispatch_tests. Range-diff versus d9431df shows only changed insertion context; production hunks unchanged. All migration entries retained and historical overlap notes corrected. Formatting and strict workspace Clippy pass; workspace tests and doctests are running locally, and new required CI has been triggered. No merge armed pending final-head evidence and approval. |
|
Local final-head validation is complete on 8a50244: cargo fmt --all -- --check; cargo clippy --workspace --locked --all-targets -- -D warnings; cargo test --workspace --locked --lib --tests; cargo test --workspace --locked --doc all exited 0. Windows no-listener, access-denied, closed-listener and silent-handshake integration tests passed. Environment-dependent ignored tests remain unrun; no fresh live-KiCad or cross-account result is claimed. GitHub reports MERGEABLE and closingIssuesReferences contains #532 only; #531 remains partial. Waiting for required hosted checks and final-head merge approval. |
neusse
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 8a50244. Previous requested changes are resolved: public error wording distinguishes uncertain receipt from proven response and authorization from ownership; TransportUnreachable compatibility is preserved; migration semantics and served-dispatch regression coverage are present. The refresh preserves both annotation and IPC-failure dispatch tests, with unchanged production hunks. Local fmt, strict Clippy, workspace tests, and doctests exited 0; all ten required hosted checks passed on this head. Exact reporter cross-account observation remains disclosed validation debt; #531 is partial. George approved this exact head for merge.
Current maintainer refresh
Rebased onto upstream/main
71d9e0cc12c6ad72c847f8eb8adbce17e51792acafter #579 and #591. New head:8a50244(full SHA recorded in the refresh comment). Both annotation and IPC-failure served-dispatch test modules are preserved. Range-diff shows only a changed insertion context; no production behavior was changed by the refresh. Required CI must rerun on this head. Evidence below referencing earlier heads is historical contributor evidence, not validation of this refreshed head.Closes #532
Part of #531
Problem
check_kicad_uiandopen_projectreduced every IPC failure to onefalse. No configured address, nothing listening, a listener that refused this account, and a listener that is not KiCad all looked the same as KiCad being closed. #532's reporter had KiCad listening while Konnect ran as the Codex sandbox account, and nothing Konnect returned could tell them that. The same root cause sat under #300 in August.send_commanddials synchronously, so NNG already returns a distinct error for each case.ping()discarded it.Measured first
These are Windows 11 named pipes dialed with Konnect's own socket options (Req0, 5 s send, 30 s receive):
ConnectionRefusedPermissionDenied, and the listener never sees a clientClosedTimedOutThe nng source confirms the Unix side.
posix_ipcdial.cmapsENOENTtoECONNREFUSED,posix_debug.cmapsEACCEStoEPERM, andipc.chard-codes a 10-second negotiation timeout. Unix sockets therefore land on the same variants. I can't run Unix here; CI's Linux and macOS test jobs pass the Unixaccess_deniedandhandshake_failedtests on this PR's head, and they are the evidence for that half.Change
konnect-ipc: the dial'snng::Erroris classified into a typedUnreachableReason(not_configured,no_listener,access_denied,handshake_failed,transport_error), never matched from message text.TransportUnreachablestays the constructible unit struct it is onmain; the reason travels in a private context layer directly above it whoseDisplayis the message, so error chains render byte-for-byte as before and old-way construction keeps compiling (classifies as unreachable, reportstransport_error).unreachable_reason(&anyhow::Error)reads that layer with anyhow'sdowncast_ref. The dial's error message explains the classified cause instead of listing every possible one.ping_outcome()returnsPingOutcome::{Responsive, Unreachable { reason, message }, RequestFailed { message }}, so a request that did not complete and may have reached the endpoint (an explicit KiCad status such asAS_NOT_READYproves receipt; a receive timeout or malformed reply does not) stays apart from one that never arrived.ping()keeps its signature and its warning log.check_kicad_uiandopen_projectaddipc_failure: {kind, message}. It isnullwhen no failure kind was established: the Ping succeeded withAS_OK, orcheck_kicad_ui's own deadline expired first (reported astimed_out: true). A request that did not complete isrequest_failed, notnull, whether or not it reached KiCad; an explicit KiCad status inmessageproves receipt.access_deniedproves an authorization refusal, not who owns the endpoint: the docs name a different account or a restrictive ACL as likely causes and same-user, out-of-sandbox operation as the known workaround.open_project'smessagefollows the kind:access_denied,handshake_failed, andrequest_failedget their own headline (recorded verbatim in the migration entry), and the rest keep the existing one.API_MIGRATIONS.mdentry (additive field; nothing removed or renamed; changedopen_project.messagewording and the reworded dial errors recorded;nulldefined).TROUBLESHOOTING.mdgains a per-kind table and the out-of-sandbox HTTP setup from Windows: open_project reports IPC unreachable although KiCad api.sock pipe exists #300.tool-directory.mdrows updated. Thecheck_kicad_uidescription names the kinds and the handshake timing limit below.Limit, stated rather than hidden: a listener that accepts and never negotiates holds the dial for NNG's fixed 10 s. That is longer than
check_kicad_ui's default 5 s budget, which reportstimed_outfirst. The description, the troubleshooting entry, and the migration note all say to passtimeout_secondsabove 10.Not changed:
IpcFailure::Unreachable(String)and every fallback decision. The reason is read only where a response reports it.attempt_ipc_write's gating is untouched.Changed tool behavior
Per
docs/RELIABILITY_CONTRACT.md(landed in #550 after the last review round), forcheck_kicad_uiandopen_project:check_kicad_ui.timeout_secondsinteger 1–300, default 5;open_project.pathoptional. No argument added.timeout_secondsreturnsinvalid_argumentnaming the field (health_timeout_is_bounded_and_typed).ipc_address,KICAD_API_SOCKET, or discovery); the result is a live Ping with no file fallback.ipc_failure.messagenames the redacted endpoint that was dialled.ipc_failure.kindis derived from the typednng::Errorat the dial, never from message text; a request that did not complete and may have reached the endpoint isrequest_failed(an explicit KiCad status proves receipt), kept apart from the unreachable kinds.ipc_failure: nullmeans no kind was established (AS_OK, ortimed_out: truefrom the check's own deadline). Each kind maps to one documented fix inTROUBLESHOOTING.md;handshake_failedneedstimeout_secondsabove 10 to be observed.Tests
The listeners in
crates/konnect-ipc/tests/unreachable_reasons.rsare real pipes and sockets. No KiCad is involved.no_listener.access_denied, and the listener's accept times out, proving the refusal happens before connection. A listener that closes reportshandshake_failed. A silent listener reportshandshake_failedin under 20 s.000reportsaccess_denied(skipped as root). A listener that closes reportshandshake_failed.mock_server_test.rs: anAS_BAD_REQUESTanswer isRequestFailed, not unreachable, and anAS_OKanswer isResponsive.nng::Errormaps to the reason whose fix applies; every reason has a distinct name and explanation; an unconfigured client reportsnot_configured; the reason is read from the marker, and a look-alike message yieldsNone.konnect-core: evidence shape;check_kicad_uiandopen_projectreportno_listenerend to end through their handlers; theopen_projectheadline follows each kind.ipc_failure_kind_and_message_survive_the_served_dispatchdrives both tools throughMcpHandler::handle_message(tools/call), andipc_failure_kind_and_message_reach_the_client_over_stdiodrives the real binary over the stdio protocol withKICAD_API_SOCKETpointing at a socket nothing listens on; both assertipc_failure.kindandmessagein the served result.Neuters (each guard reverted alone, then the listed tests run):
PermissionDeniedclassified astransport_erroreach_dial_error_maps_to_the_reason_whose_fix_applies,windows::a_pipe_that_refuses_this_account_reports_access_deniedping_outcomedrops the typed reasonan_address_nothing_listens_on_reports_no_listener,an_unconfigured_client_reports_not_configured, and all three Windows listener testscheck_kicad_uireturnsipc_failure: nullcheck_kicad_ui_names_why_ipc_did_not_answeropen_projectignores the kind for its headlineopen_project_says_kicad_answered_when_it_answered_with_an_errorunreachable_reasonignores the detail layer (reportstransport_errorfor everything)the_reason_is_read_from_the_marker_never_from_the_text,an_unconfigured_client_reports_not_configured,an_address_nothing_listens_on_reports_no_listener, and the three Windows listener testsThe last row is why the second commit exists. On the first pass, that neuter left every test green: the only handler test reached
no_listener, whose headline is the generic one. A mock answeringAS_NOT_READYnow drives the handler torequest_failed. My first attempt at the third neuter didn't compile, so it proved nothing; it was redone with a replacement that builds. All four neuters were run with--no-fail-fast, so the real-pipe tests are shown catching them as well as the unit tests.Head
104c5aa(one commit one33b9a0): CI 10/10. Local gate on the same head: fmt, clippy-D warnings,--lib --tests1842 passed / 0 failed,--doc,cargo xtask fix-doc-counts --checkunchanged,reliability_contract2 passed / 0 failed.End to end, through the release binary over stdio
e2e532.pystartskonnect.exewith onlyKICAD_API_SOCKETset, then callscheck_kicad_uiandopen_projectagainst stand-in pipes bound by a separate process, and against pcbnew 10.0.5.check_kicad_uiipc_failure.kindopen_projectheadlineno_listeneraccess_deniedhandshake_failedtimeout_seconds: 5timed_out: truenulltimeout_seconds: 15handshake_failedno_listener(its server was not up yet)ipc_responsive: truenullAn example message, verbatim:
Cannot connect to KiCad IPC at ipc://C:\konnect-e2e532\readonly.sock: Permission denied. The operating system refused this account access to the endpoint. Likely causes: the endpoint was created by a different account, as when a sandboxed AI client (for example Codex on Windows) runs Konnect as a separate user, or a restrictive ACL. Known workaround: run Konnect as the same operating-system user as KiCad, outside the sandbox. Guide: https://github.com/mixelpixx/Konnect/blob/main/docs/TROUBLESHOOTING.mdI could not create a second Windows account, so the cross-account case itself was not run. The stand-in reproduces its access check: a descriptor that grants this account nothing beyond Everyone's read access.
Overlap
Earlier #523/#528/#530 overlap notes are obsolete after their integration into main. This refresh resolves the #579 overlap in
mcp/handler.rsby preserving both independent test modules. All existing API migration entries are retained. No tools or generated counts changed.🤖 Generated with Claude Code