Skip to content

fix(cli): use the daemon distro for remote planning and MCP discovery - #626

Merged
vladimirrott merged 6 commits into
lacs-project:mainfrom
QinXi-ai:fix/remote-daemon-distro
Oct 26, 2026
Merged

vladimirrott merged 6 commits into
lacs-project:mainfrom
QinXi-ai:fix/remote-daemon-distro

Conversation

@QinXi-ai

@QinXi-ai QinXi-ai commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #618. CLI planning, routing, MCP discovery and doctor use the target daemon's distribution instead of the controller's. A Fedora controller connected to an Ubuntu daemon retains Ubuntu read tools and the Debian planning prompt. The optional, serde-defaulted host_distro field preserves compatibility with older daemons; unavailable or older daemons still fall back locally with a warning.

The MCP regression mock now accepts connections with a deadline and bounds socket reads and writes. Reverting the MCP daemon-distro lookup on an Ubuntu host produces a clear failure after about 20 seconds instead of hanging on accept() / join(). This addresses review 5632185343. Existing startup fallback warnings and restart guidance remain intact.

Validation

  • After merging upstream 8532cf9, the mcp_stdio and remote_distro integration targets pass: 6 tests, 0 skipped.
  • Reverse mutation on that same base: replace the MCP constructor's target lookup with local fallback. The targeted regression fails after 20.024 seconds with MCP server did not connect to query the daemon's distro. Restoring production code returns all 6 integration tests to passing.
  • cargo clippy --locked --offline -p sysknife-cli --all-targets --all-features -- -D warnings, formatting and whitespace checks pass.
  • GitHub Rust job at b1d3de7 completed the full workspace suite: 2002 passed, 6 skipped. The job then failed only because the inherited baseline still said 1998. The follow-up baseline commit uses that measured count and synchronizes the three published figures. Public-claim and baseline-provenance checks pass.
  • Current-head Rust CI at 586178a passes: 2002 passed, 6 skipped, with test_baseline: 2002 rust tests, matching the recorded baseline. All current-head checks have finished successfully except the intentionally skipped container-smoke job. The full local board is not claimed green.

Local validation limits

scripts/ci-local.sh --no-postgres was run after synchronizing main. Workspace Clippy, rustdoc and nextest are blocked by missing GLib/GIO/GObject development libraries in WSL. Frontend/setup checks encounter local npm/Node execution and dependency-install problems. Missing lint/security helper tools leave other checks incomplete, and the live Postgres check was explicitly skipped locally. The GitHub frontend, Postgres, dependency-review, security-audit, scripts-lint and secret-scan jobs passed on the tested code head.

This contribution is AI-assisted.

Local CI summary
PASS  rust: cargo fmt --all --check
FAIL  rust: cargo clippy --workspace --all-features --all-targets --locked -- -D warnings
FAIL  rust: cargo doc (RUSTDOCFLAGS=-D warnings)
FAIL  rust: cargo nextest run --workspace --locked (+ test baseline)
FAIL  frontend: npm ci (falls back to npm install)
SKIP  frontend: npm audit / tsc / vitest -- SKIPPED (dependency install failed)
PASS  hygiene: firewall backend reporter fixtures
PASS  hygiene: check_repo_completeness.sh
PASS  hygiene: check_test_reachability.sh
PASS  hygiene: check_release_versions.sh
FAIL  hygiene: verify-action-pins.sh
FAIL  hygiene: npm test --prefix packages/setup
PASS  hygiene: tests/release/action-pin-comments.test.sh
PASS  hygiene: tests/release/action-steps.test.sh
PASS  hygiene: tests/release/audit-export-confidentiality.test.sh
PASS  hygiene: tests/release/cargo-audit-schedule.test.sh
PASS  hygiene: tests/release/cassette-replay-parity.test.sh
PASS  hygiene: tests/release/ci-local.test.sh
PASS  hygiene: tests/release/codex-plugin-manifest.test.sh
PASS  hygiene: tests/release/crates-index-poll.test.sh
PASS  hygiene: tests/release/database-path-agreement.test.sh
FAIL  hygiene: tests/release/docs-share-cards.test.sh
PASS  hygiene: tests/release/fail2ban-ban.test.sh
PASS  hygiene: tests/release/grub-kargs-edit.test.sh
PASS  hygiene: tests/release/install-paths.test.sh
PASS  hygiene: tests/release/log-edit.test.sh
FAIL  hygiene: tests/release/macos-survey-command.test.sh
PASS  hygiene: tests/release/markdown-link-files.test.sh
PASS  hygiene: tests/release/mdbook-links.test.sh
PASS  hygiene: tests/release/mount-edit.test.sh
PASS  hygiene: tests/release/no-secrets.test.sh
PASS  hygiene: tests/release/node-eol.test.sh
PASS  hygiene: tests/release/npm-audit-off-pr-path.test.sh
PASS  hygiene: tests/release/postgres-contract-guard.test.sh
PASS  hygiene: tests/release/public-claims.test.sh
PASS  hygiene: tests/release/registry-manifest.test.sh
PASS  hygiene: tests/release/release-crates.test.sh
PASS  hygiene: tests/release/release-create-rerun.test.sh
PASS  hygiene: tests/release/release-publish-order.test.sh
FAIL  hygiene: tests/release/release-rehearsal.test.sh
PASS  hygiene: tests/release/release-version-bump.test.sh
PASS  hygiene: tests/release/release-version-check.test.sh
PASS  hygiene: tests/release/release-version-pins.test.sh
PASS  hygiene: tests/release/rmswap.test.sh
PASS  hygiene: tests/release/scheduled-job-edit.test.sh
PASS  hygiene: tests/release/shellcheck-coverage.test.sh
PASS  hygiene: tests/release/smithery-manifest.test.sh
PASS  hygiene: tests/release/sudoers-edit.test.sh
PASS  hygiene: tests/release/systemd-directory-modes.test.sh
PASS  hygiene: tests/release/test-baseline-provenance.test.sh
PASS  hygiene: tests/release/test-reachability.test.sh
PASS  hygiene: tests/release/tracked-eol.test.sh
PASS  hygiene: tests/release/version-sites.test.sh
PASS  hygiene: tests/e2e/provider-parity.test.sh
PASS  hygiene: tests/e2e/story-metadata.test.sh
PASS  hygiene: tests/e2e/story-runner-verdicts.test.sh
PASS  hygiene: tests/e2e/ubuntu-vm-bootstrap.test.sh
PASS  hygiene: tests/e2e/vm-env-secrets.test.sh
PASS  hygiene: tests/e2e/vm-sync-parity.test.sh
WARN  hygiene: markdownlint-cli2 -- SKIPPED (not found; install: npm install --global markdownlint-cli2)
WARN  hygiene: markdown-link-check -- SKIPPED (not found; install: npm install --global markdown-link-check)
WARN  hygiene: yamllint -- SKIPPED (not found; install: pip install yamllint)
WARN  hygiene: shellcheck -- SKIPPED (not found; install: sudo apt-get install shellcheck)
WARN  security: cargo audit -- SKIPPED (cargo-audit not found; install: cargo install cargo-audit --locked)
SKIP  postgres-contract: live Postgres contract (store + CLI anchor exit code) (--no-postgres)
=========================================
ci-local: FAIL (9 failing check(s))
WARNING: REQUIRED CI check(s) did not run: missing-tool:markdownlint-cli2 missing-tool:markdown-link-check missing-tool:yamllint missing-tool:shellcheck missing-tool:cargo-audit postgres-contract
Install markdownlint-cli2, or rerun with --allow-missing-tools only when you intentionally accept that CI gap.
Install markdown-link-check, or rerun with --allow-missing-tools only when you intentionally accept that CI gap.
Install yamllint, or rerun with --allow-missing-tools only when you intentionally accept that CI gap.
Install shellcheck, or rerun with --allow-missing-tools only when you intentionally accept that CI gap.
Install cargo-audit, or rerun with --allow-missing-tools only when you intentionally accept that CI gap.
To run postgres-contract, set SYSKNIFE_TEST_POSTGRES_URL or install podman, then rerun without --no-postgres.
ci-local: INCOMPLETE (required checks skipped)

@vladimirrott vladimirrott left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, @QinXi-ai, and fast: #618 went up Monday and this covers all of it. All five call sites go through TargetDistro, the #[serde(default)] field keeps both directions compatible, and the fallback warns instead of guessing. Reviewed at 69564c7.

What I checked:

  • On the head, distro::detect survives only inside TargetDistro's fallback and the daemon's own detection.
  • Old daemon, new client: a missing or null host_distro, or an unknown variant (I sent {"Arch": …}), resolves to None and falls back with the warning. New daemon, old client: the CLI and the shell read named fields, so the extra field is ignored.
  • Trust: the reported distro steers the prompt, the tool list and the client-side routing pre-check. validate_action_platform still decides on the daemon side from its own detection, and DistroId::Other strings reach the prompt through normalise_free_text like before.
  • End to end: I ran your binary as sysknife mcp-server inside fedora:41 with no network, against a mock daemon on a mounted socket. Ubuntu 24.04 reported → 61 tools including sysknife_ufw_status. Fedora reported → 38. No field, or an unknown variant → 38 plus the fallback warning. That's the issue's split, fixed.
  • Reverting the runner lookup makes planning_and_doctor_use_the_remote_distribution fail at remote_distro.rs:118.
  • fmt, clippy --all-targets and doc pass. The red rust job is only the test count (1,997 vs the recorded 1,993). CONTRIBUTING says I refresh that at merge, so leave it.

One thing to fix first. Reverting the MCP lookup in SysknifeMcpServer::new doesn't make tools_list_uses_the_daemons_distro_and_falls_back_for_old_daemons fail; it hangs. The mock daemon's thread blocks in listener.accept() (mcp_stdio.rs:134) and the test join()s it at :176. On an Ubuntu host the Ubuntu case passes its assertion without the server ever dialling, then the join never returns. ci.yml sets no timeout-minutes, so CI would sit for six hours instead of going red. Either accept with a deadline, the way remote_distro.rs already does, or assert the mock saw a query_state before joining. Putting the Fedora case first would also make a revert fail on an Ubuntu runner.

Smaller notes, none blocking:

  • The fallback warning says "unreachable or too old", and it also fires for an unknown variant or a failed daemon-side detection.
  • The MCP server now makes a blocking QueryState (a full collect_state, up to the 10 s socket timeout) before it serves stdio, and run_intent adds a second one per run. Fine for now; worth one call later.
  • The unit tests near mcp_server.rs:2642 that call SysknifeMcpServer::new() now dial the default socket on the developer's machine.

#628 fixed the red security-audit on main this afternoon, so updating the branch clears it.

@vladimirrott vladimirrott left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for syncing the count, @QinXi-ai. The board is green at 54e6a5c now, rust and security-audit included. Re-reviewed at 54e6a5c.

The new commit touches four files, and none of them is a test:

$ git diff --stat 69564c7 54e6a5c
 README.md                           | 2 +-
 docs/distro-support.md              | 2 +-
 docs/introduction.md                | 2 +-
 tests/evidence/workspace-tests.json | 2 +-

So the one blocking item from my last review still stands. apps/sysknife-cli/tests/mcp_stdio.rs is byte-identical to 69564c7, which means reverting the MCP lookup in SysknifeMcpServer::new still leaves tools_list_uses_the_daemons_distro_and_falls_back_for_old_daemons blocked in the mock's listener.accept() (mcp_stdio.rs:134) with the test waiting on join() at :176. On a runner where the host distro matches the reported one, the test hangs instead of failing. Either of the two fixes I suggested works: accept with a deadline the way remote_distro.rs already does, or assert the mock saw a query_state before joining.

Everything else from that review holds, and the smaller notes there stay optional. Once the test fails cleanly with the lookup reverted, this is ready from my side.

@vladimirrott vladimirrott left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for sticking with this one. The non-blocking accept loop with the FRAME_TIMEOUT deadline resolves the hang I flagged: a reverted lookup now fails the test instead of stalling it.

I proved it at 586178a6 in a no-network container. tools_list_uses_the_daemons_distro_and_falls_back_for_old_daemons passes as written. I then swapped the lookup in SysknifeMcpServer::new back to local detection:

s/DaemonClient::new(resolve_socket_target()).target_distro();/crate::client::TargetDistro::resolve(None);/

and the same test FAILED. I also traced the other three paths to the same lookup: run_intent, run_doctor and plan_intent_inner. The daemon still decides eligibility in validate_action_platform, so a wrong host_distro can only shape the prompt and the tool list. The #[serde(default)] field plus the unknown-variant fallback keeps an old daemon working, and your test covers that None case. The count of 2002 matches across the artifact and the three docs.

Two leftovers I'm happy to take as follow-ups rather than hold this:

  • SysknifeMcpServer::new().get_info() in the unit tests at mcp_server.rs:2642 and :2649 now dials the developer's default socket. It's harmless, but for_distro(None) would keep those tests offline.
  • The warning still says "unreachable or too old", while the docs add "or cannot identify it". Worth one wording pass someday.

Approving and merging. This closes #618, and I'll write the CHANGELOG line at release. I'll look at #684 next.

@vladimirrott
vladimirrott merged commit d6baae9 into lacs-project:main Oct 26, 2026
13 checks passed
vladimirrott added a commit to QinXi-ai/sysknife that referenced this pull request Oct 27, 2026
Main reached 2002 with lacs-project#626; this branch adds two tests
(every_prompt_action_is_offered_by_its_plan_schema and
other_prompt_preserves_examples_and_has_an_available_preference_finish),
so the artifact and the three published figures move together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpyrhbDTW1gBwzGAC44GYU
vladimirrott added a commit that referenced this pull request Oct 27, 2026
* fix(brain): keep other-host prompts within the action schema

* test: move the workspace baseline to 2004 for the two new prompt tests

Main reached 2002 with #626; this branch adds two tests
(every_prompt_action_is_offered_by_its_plan_schema and
other_prompt_preserves_examples_and_has_an_available_preference_finish),
so the artifact and the three published figures move together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpyrhbDTW1gBwzGAC44GYU

---------

Co-authored-by: hushen <190065939+918154429@users.noreply.github.com>
Co-authored-by: Vladimir Rotariu <rotariuvladimir@gmail.com>
Co-authored-by: Vladimir Rotariu <sysknife-development@protonmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
vladimirrott added a commit to Georgefifth/sysknife that referenced this pull request Oct 27, 2026
Main moved to 2,004 Rust tests (lacs-project#626, lacs-project#684); this branch adds seven, so
the artifact and the three published figures read 2,011. No other
conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpyrhbDTW1gBwzGAC44GYU
vladimirrott added a commit that referenced this pull request Oct 27, 2026
Minor release. #627 keys request_hash with an HMAC under the audit key
(chain_version 4; previews stored by an older release fail closed after
upgrading) and #639 changes the history request a newer CLI sends, so a
caller relying on the old behaviour notices: the middle digit moves.

Also: the planner sees only its host's distribution (#626, #656, #658,
#684), checkpoint and the MCP audit tools share the audit-store resolver
(#636), every workflow job has a time limit (#662), a weekly cargo audit
(#640), release hardening (#635, #637, #660), and every published figure
is rendered from its source (#685).


Claude-Session: https://claude.ai/code/session_01UpyrhbDTW1gBwzGAC44GYU

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

Planning against a remote daemon uses the client's distro, so a Fedora laptop loses the Ubuntu tools for an Ubuntu VM

2 participants