Working with CI

September 9, 2026 ยท View on GitHub

CI checks the complete pull request from its merge base, so unrelated changes added to the target branch do not count as changes in the PR. A README update inside a PR that also changes runtime code still receives the runtime checks. A PR containing only README files, docs/, or assets/readme/ skips runtime tests and builds, while formatting, secret scanning, and dependency review remain active. Unknown paths and missing comparison history run checks.

The classification lives in scripts/ci_scope.py, used through .github/actions/ci-scope. Runtime test jobs keep their existing check names when their heavy steps are unnecessary, so required checks do not wait for a workflow that never started. Desktop bundle jobs retain their existing scope.

What a passing check means

CheckWhat it verifies
Python 3.12, 3.13, and 3.14The full backend suite on Ubuntu 24.04
Windows lifecycleReal file locks, ACLs, recovery, background service operations, and discovery races
BrowserChromium interactions with the real local service and a deterministic test Agent
Python packageDistribution metadata, packaged resources, and installation in a clean environment
Desktop qualityFrontend tests, types, protocol consistency, Rust formatting, lint, and tests
Four platform bundlesBuild artifacts, packaged runtime startup, resources, and platform package checks
SecuritySecret history, dependency vulnerabilities, and dependency licenses

Browser CI does not call a paid model. Live-provider, native GUI, and actual OS login/reboot acceptance remain separate from these regression checks. Functional startup tests use the launcher's readiness policy. Their timeouts are test budgets, not published startup-performance guarantees.

Reproduce the Python test environment

From the repository root, create and activate a Python 3.12, 3.13, or 3.14 virtual environment. Then run:

python -m pip install -r scripts/ci/requirements.lock
python -m pip install --no-deps --no-build-isolation -e '.[test]'
python -m pip check
python -m pytest -q --durations=10
pre-commit run --all-files

The lock contains the package's runtime/test dependencies and CI tools. Runtime versions shared with Desktop follow desktop/sidecar-requirements.lock. --no-deps prevents editable installation from silently adding unlocked runtime dependencies; pip check reports missing or incompatible requirements.

After changing package requirements, test extras, or the sidecar lock, regenerate the CI lock with uv and commit the result:

uv pip compile scripts/ci/requirements.in --python-version 3.12 --universal --no-emit-package deepcode-hku --output-file scripts/ci/requirements.lock

Add --upgrade when deliberately refreshing all compatible dependency pins. Run tests and the security checks after refreshing. The security workflow audits the CI lock as well as the packaged runtime. Package installation and the live package dependency audit still resolve supported dependency ranges, so the fixed test baseline does not replace installation compatibility checks.

Diagnose a failure

Inspect the earliest failing step. Python and Windows jobs upload JUnit reports with test names and durations; browser jobs retain failure traces and screenshots. Python logs name each test as it runs. If one test takes longer than 60 seconds, pytest prints all Python thread stacks; this is diagnostic output, not an extended deadline or a successful result. Unhandled background-thread exceptions and unraisable exceptions fail the test instead of appearing only as warnings. Packaged startup errors include worker output. A failure while stopping the test service is attached to the original exception, and temporary cleanup is still attempted. A cleanup failure on its own also fails the check.

PR updates cancel superseded runs. Rust dependency and pre-commit caches reduce repeated setup; caches do not substitute for tests or package validation. A cold cache must produce the same verdict as a warm cache. Failed tests are not automatically retried until they turn green.

Keep concurrency tests independent of machine speed

Submitting a Turn or observing an Automation Run as RUNNING does not guarantee that the Agent has started consuming its input. Before interrupting a scripted Agent whose next step depends on consuming the current step, wait for an explicit signal from that Agent. Assert execution counts after the relevant work settles. Do not use a fixed sleep as evidence that work started or finished.

The automation Goal-run suite runs every scenario with immediate and deferred Agent dispatch. The deferred variant deliberately delays Agent entry to expose assumptions about thread scheduling; it does not retry a failed test. Both variants must pass in each Python version. When a race is found, first reproduce the adverse ordering, then fix the synchronization and retain coverage for that ordering.