Skip to content

Contributing

Read Security, Architecture, and AGENTS.md before changing behavior. Open an issue first for a new MCP tool, a public schema or error change, a relaxed limit, or a lifecycle change.

These are contracts rather than style preferences: startup authenticates nothing; every operational channel is mux-only; transport loss never silently reconnects; sudo remains passwordless and cache-independent; public errors do not leak private paths, subprocess argv, or secrets.

Environment

Use Linux with CPython 3.13 or 3.14, Bash, rootless Podman, Git, OpenSSH, rsync, GNU coreutils, and GNU Make. Run commands from this project root. Make is the supported interface; make help lists its main targets.

make runtime-venv is the explicit installation step for running the server. It contains runtime packages only. Development and lint environments are separate; do not install project requirements into a system or user Python.

Change Loop

make format
make check

Use strict typing, keep tests parallel-safe, and do not weaken lint, security, or coverage policy to make a failure green. Unit tests must use fake SSH, rsync, and sudo programs rather than real identities or hosts.

make check includes lint and format validation, strict mypy, Bandit, branch coverage, syntax and shell checks, network and container confinement, reproducible locks, minimum-Python compatibility, workflow validation, policy, and version checks. make ci additionally runs the reviewed online dependency audit.

When changing startup, packaging, or dependencies, also run make standalone smoke-standalone on the current native architecture. CI runs the same contract independently on amd64 and arm64.

Review Expectations

  • Fix types rather than adding unchecked Any, casts, or ignores.
  • Do not add lint or security suppressions without a precise local reason.
  • Match the surrounding naming, error handling, and comment density.
  • Explain why a constraint exists rather than narrating the next line.
  • Keep detailed material under doc/ and the root README concise.

Tests

Every behavior change needs a test that fails without it. Fakes should assert exact argv, one authentication, mux-only reuse, output bounds, and cleanup. The default suite runs through pytest-xdist, so tests cannot depend on order, fixed ports, or shared mutable paths. Host-dependent launcher and Podman tests carry the host marker and are excluded from ordinary pytest.

Every pytest run enforces the branch-coverage threshold stored only in pyproject.toml. Raise it deliberately when coverage improves; never lower it to hide a regression. A test must leave no generated state in the checkout.

Debugging Silent Failures

When correct output is followed by a hang, unexplained timeout, or silent process exit, trace the real failing path instead of varying it by trial:

strace -f -e trace=execve,wait4,exit_group,kill -o trace.log <command>

exit_group(127) indicates a program was not found; ENOENT on execve identifies it. A disposable tracing container needs SYS_PTRACE, an unconfined seccomp profile, and root; never weaken the normal test container.

Live Acceptance

The unattended matrix requires the explicit runtime and creates its own ephemeral Ed25519 identity:

make runtime-venv
make live-preflight
make live-test

Hardware-token targets remain operator controlled. Supply key paths only as runtime arguments, never in source, documentation, fixtures, or logs. The sanitized variant removes the recoverable session allowlist from the MCP child and proves one authentication through the user-systemd askpass route. It is not part of CI or another aggregate target.

Dependencies And Documentation

Direct pins belong in the matching requirements*.in file. The development, standalone, and documentation inputs extend requirements.in; the lint input is independent. Run make lock and review all five generated .txt locks after a direct dependency change. Do not hand-edit a lock or add a dependency when a small standard-library implementation suffices.

Documentation is part of the same atomic change. Synchronize CLI options, tools, schemas, limits, annotations, errors, security behavior, examples, and the applicable maintainer contract. Policy tests require local documentation links to remain relative and resolvable. Run make docs-audit after changing public or maintainer documentation.

Keep a pull request focused on one behavior. Explain the security and cleanup effects and list the exact validations performed. Documentation and client examples are part of the same change, not follow-up work.

The SSH library is a published hash-locked dependency, not vendored code or an alternate import path. Changes to its public API must be validated independently and against this consumer.

Pull Requests And Releases

Use an imperative subject and explain the need, security and cleanup effects, and exact validation. Third-party workflow actions remain pinned to full commit SHAs. The pull-request workflow has read-only contents permission. Dependency submission and release run only on trusted direct pushes to main; write access stays confined to the individual job that needs it.

Changing CHANGELOG.md triggers the dedicated PR metadata workflow. It copies the newest populated changelog section into a marker-delimited block in the pull-request body while preserving all manual text. Do not edit or duplicate the marker lines; edit the changelog source or write outside the managed block.

For a released-product change, update .version, run make version-sync, and add one matching dated CHANGELOG.md section. Validate with make version-check and render the exact release body with make release-notes.

Do not report a vulnerability in a public issue. Use private vulnerability reporting or contact the code owner. Include the affected version, crossed trust boundary, and a minimal reproduction without credentials, private keys, or real hostnames.