cujo
Skip to content
Contents

Configure a repo

What .cujo.yml accepts, which branch each key is read from, and why that is not the same branch.

The whole file

Optional, and every key in it is optional. Missing keys are inferred from the repository’s own build files — pyproject.toml, package.json, a Makefile, a CI workflow. Write only what Cujo got wrong.

mode: diff
install: uv sync
test: uv run pytest
boot: uv run uvicorn app:app --port 8000
smoke:
  - GET /health
  - GET /orders/1
allow_hosts:
  - api.stripe.com
discord_guild: "222222222222222222"

Two readers, and two branches

Six of these keys are read from the pull request’s base tree — five by the agent in the sandbox, and mode by the service before there is a sandbox. The seventh is read by the service from the repository’s default branch. None is read from the pull request’s own code.

That is the point of the whole design. Policy that a pull request could edit is not policy: a change could add its own exfiltration host to allow_hosts in the same commit that calls it. And a declaration that has to be merged is proof that whoever made it controls the repository, which is what makes it worth anything.

If a pull request changes .cujo.yml, the run records a warn saying so and uses the base version anyway. The edit takes effect once it is merged, like any other policy change.

The keys

KeyRead fromMeaning
modebaseWhich review a pull request gets: sandbox runs it (the four checks below), and diff reads it against the repository’s own standards files on a cheap model, with no sandbox and findings of at most warn. Absent means the instance’s default, which is sandbox unless the operator changed it. Two floors override either answer: a pull request that changes a dependency manifest, or one a Bot account opened, is always the sandbox.
installbaseHow to install the repository. Inferred when absent.
testbaseHow to run the suite. If it is absent and cannot be inferred, the run stops with one warn — “no test suite found” — and no checks are spawned at all.
bootbaseHow to start the app, for the smoke check.
smokebaseEndpoints to hit once it is up, each written METHOD /path.
allow_hostsbaseHosts this repository legitimately reaches. Anything contacted that is neither listed here nor a known package index counts as unknown egress. This is the one key that appears in no build file, so it is the one key that can never be inferred — if your build talks to a host, only you can say so.
discord_guilddefault branchWhich Discord server may receive this repository’s cards, as a quoted id. Half of a two-part binding; see Discord notifications.

mode and discord_guild are extracted by a single strict line match rather than parsed as YAML, so a malformed value is not an error — it simply means the repository has declared nothing. A configuration typo must never cost a review.

When the file cannot be read

Four outcomes, and they are not the same outcome:

  • read — the policy is used.
  • absent — there is no file. Everything is inferred, and no host is allowlisted.
  • too_large — over the size cap. The agent reads the file itself rather than proceeding on half a policy.
  • unreadable — a symlink pointing out of the checkout, or an I/O error. The run stops and says so. Half a policy is worse than none, because the missing half is usually the allowlist.