What a fresh clone needs before pnpm install, pnpm test, and cargo test all work.
The quick start in ../README.md is enough to run the web stack. Read this page when
an install or a test run fails on a machine that looks correctly configured. The two that show up
most are an unbuilt common package and missing GTK headers, and neither error message names its own fix.
| Tool | Version | Notes |
|---|---|---|
| Node | >= 22 | .nvmrc pins the exact version |
| pnpm | >= 11 | Use it exclusively. Never npm, yarn, or bun. |
| Docker | Engine or Desktop | Needed for the stack, integration tests, and e2e |
| Rust | stable | Desktop app only. Skip unless you build packages/desktop. |
pnpm install does not need a GitHub Packages token. The stylesheet, canon file, and company logo
files the Hub carries are copied into this repo and listed in
packages/frontend/ci-common.vendor.json. Text copies
carry a vendored-from: line. Search that string when a CI Common package changes, then refresh
the copies from a checkout:
node scripts/vendor-ci-common.mjs --from ../ci-common --ref <tag>
node scripts/vendor-ci-common.mjs --checkPass a commit or a tag that exists in your checkout (git -C ../ci-common fetch --tags), not HEAD: the script reads files with git show <ref>:<path>, and a stale local main has none of the newest files. CI Common tags each package on its own, so one --ref carries all three only when they were released together; check that the version it writes for tokens, config and assets is the one you meant.
CI Common keeps its lint gate as packages/config/bin/lint-canon.ts, which tsup builds. The file has no type syntax, so the manifest vendors it byte for byte into packages/frontend/scripts/lint-canon.mjs and lint:canon runs it with plain node. If a later release adds real TypeScript to it, lint:canon fails to parse the copy; vendor the built dist/lint-canon.js instead.
The Hub's own mark is not vendored. It lives in packages/frontend/hub-brand, which is the origin for the favicons, app icons, and header logo; pnpm --filter frontend brand:sync mirrors it into place.
Build packages/common before running backend tests — the backend's vitest config aliases
@ci-hub/common/schemas and /types to source, but /validation resolves through the package's
exports to dist/, so an unbuilt common fails 26 backend test files with
Cannot find package '@ci-hub/common/validation':
pnpm --filter common buildThe system library `dbus-1` required by crate `libdbus-sys` was not found.
Tauri needs GTK and WebKit development headers that most distributions do not install by default.
dbus-1 is only the first one the build reaches; installing it alone moves the failure along.
Install the full set — the same list desktop-tests.yml
installs on its runner:
sudo apt-get update && sudo apt-get install -y build-essential pkg-config libssl-dev libdbus-1-dev libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev libxdo-devVerify before rebuilding:
pkg-config --exists gtk+-3.0 webkit2gtk-4.1 dbus-1 && echo okMost of the desktop crate never touches Tauri — hub_manager (the Docker/host engine),
docker_engine, port_manager, hub_env, hub_names, error_reporting, and sentry_scrubber
contain no tauri:: reference between them. This script copies those into a scratch crate with only
their real dependencies and runs their tests there, so you can verify a change without the GUI stack:
packages/desktop/src-tauri/scripts/test-without-gui.shPass a git ref to test that instead of the working tree, which is how you compare a refactor against the commit it started from:
packages/desktop/src-tauri/scripts/test-without-gui.sh origin/devIt covers 155 of the crate's tests. main.rs, tray.rs, and commands/ are genuinely Tauri-coupled
and still need the full toolchain, so treat this as a fast local gate rather than a replacement for
desktop-tests.yml.
Building the desktop app for release needs a few more (patchelf, libfuse2, rpm,
xdg-utils); see desktop-build.yml.
pnpm run check:pr is the closest local equivalent to a full PR run:
pnpm run check:prThat is lint:ci, knip:ci, check:workflows, tsc, check:openapi, test, and cargo test. Because automatic CI
triggers are gated (see CI.md), running this locally is not optional — nothing runs it
for you on push.
Individual lanes, when you want a faster loop:
pnpm --filter backend exec vitest run # backend only
pnpm exec vitest run --config scripts/vitest.config.ts # cihub CLI only
pnpm exec biome check . # lint and formatCompare against these before assuming your change broke something. Captured 2026-09-06 on dev.
| Lane | Files | Tests |
|---|---|---|
| backend | 223 (+1 skipped) | 3,102 |
| frontend | 162 | 1,421 |
scripts (cihub) |
27 | 675 |
| common | 13 | 201 |
| openclaw-plugin | 3 | 44 |
A lane that fails identically before and after your change is a pre-existing problem, not yours. Capture the baseline first when you touch anything broad.
CI.md— why workflows are gated and how to dispatch one. That file is generated byci-local docs; do not hand-edit it.../CONTRIBUTING.md— conventions for issues, PRs, and module structure.agent/AGENT_WORKFLOW.md— the workflow coding agents follow.