skip to content
all docs

Verifying your work — th run

th run takes the diff sitting in your working tree, applies it to a clean clone of the target repository, and runs the project's own install and test commands inside a container. You get a verdict, and a preview URL you can open to read the whole run.

It is for the gap between "the tests pass on my machine" and "I submitted it and waited." You find out before the poster does.

What you need

Docker running, and network access for the first clone of the target. Nothing else — the engine ships inside the CLI.

The short version

From inside the workspace claim start (or claim slice) delivered for a poster's task:

th run

The claim ledger knows which claim the workspace belongs to and which commit it was delivered from, so it fills in --claim and --sha. The diff runs from that commit to your working tree, so work you have already committed is tested too: the same change claim submit sends.

  • A whole-repository delivery is its own target. th run clones the delivered commit out of the workspace itself, so a private repository needs no credential.
  • A delivery of only the files you were granted needs the clone URL of the full repository: th run --target <url>. If you don't have access to it, skip the local run. The poster-side run after claim submit still happens.

Anywhere else, th run tests only your uncommitted changes, and you pass all three yourself, as flags or once in .th-run.json:

th run --claim <id> --target https://github.com/koajs/koa.git --sha <40-hex>

Run it again and again while you work; add --watch to re-run on every save.

Defaults live in .th-run.json in your checkout, so a claim you run repeatedly needs no flags at all. Every flag overrides the file.

Inside a claim workspace, a run tests your change against the commit you were delivered. When you submit, the poster-side run puts your change on top of the repository's default branch as that branch stands then, on a branch of its own for the poster to review. If the default branch has moved since delivery, the two results can differ. A run inside a claim workspace prints a line reminding you of this, unless you pass --json.

Options

Flag What it does
--claim <id> The claim this work belongs to. Inside a claim workspace, read from the claim ledger
--target <git-url> Repository the work is verified against. A whole-repository delivery supplies its own
--sha <40-hex> The commit your diff applies on top of. Inside a claim workspace, the commit it was delivered from
--slice <a,b,c> Files this claim shares. A diff touching anything else is refused before any container starts
--local <dir> Checkout to read the working diff from (default: the current directory)
--watch Re-run whenever a file in the checkout changes
--keep <seconds> Hold the preview open this long (default: until you press Ctrl-C)
--no-preview Skip the preview URL
--json Print the result as JSON instead of a report
--test-command <cmd> Override the derived test command. The override is recorded in the result
--placement <kind> Where the run happens: local-docker (default) or hosted. See Where it runs

How a run is fenced

A run is two steps, and they are fenced differently on purpose.

Install gets a network, through one door. Dependencies have to come from somewhere, so the install step reaches the outside only through an allowlisting proxy on its own internal network. There is no route around it.

Tests get no network at all. The test step runs with networking switched off entirely — no interface, no proxy, nothing to reach. A test suite that quietly depends on a live service fails here, which is the point: so does the poster's CI.

The container drops all capabilities, forbids privilege escalation, and caps process count. The repository's own code never runs anywhere else.

The preview

Unless you pass --no-preview, a local run ends with a URL. Opening it shows the same result the terminal printed — the verdict, the test output tail, and what was actually run.

The preview binds to loopback only. It is reachable from your machine and nowhere else, and it stops when you press Ctrl-C or when --keep runs out. Expiry and revocation beyond that are not built yet.

A hosted run cannot serve a preview yet — that would need ingress to the venue, which is not built — so a credentialed hosted terminal run must pass --no-preview. Without it the run refuses at the preview step, after the suite has already run, rather than print a URL it cannot serve.

Where it runs

By default a run happens on your machine, in your Docker daemon. That is what --placement local-docker means.

--placement hosted asks for a hosted pool: a fresh cloud VM booted for your run and torn down after it — teardown is attempted per run, and the platform deletes the instance at its one-hour lifetime either way — so the verification happens on a machine neither you nor the poster operates. It needs cloud credentials on the machine that launches it, so it is meant for our own dispatch worker rather than a developer's terminal — a bare th run --placement hosted without those credentials refuses before any machine is booted, and nothing about your work leaves your machine when it does. A run's record says what it can prove: a hosted record our intake accepted carries a Google-signed machine identity the intake checked, a run on the worker's own machine carries only what the venue reported about itself, and an absent identity proves nothing about where a run happened — a hosted run that failed partway, or whose result never reached the intake, leaves none.

A refusal exits 2, not 1. In this command 1 means your tests ran and failed; 2 means we declined to do something — usually to run them at all, and in the hosted-preview case above, to publish what we cannot serve. A script can tell the two apart, and nothing we refuse to do will ever be reported as a fault in your work.

What a result is, and is not

A run tells you what the project's own tests did, on a clean tree, with your diff applied. That is useful and it is checkable.

It is not an attestation that anything ran on hardware anyone can vouch for. The result records this about itself in plain terms rather than implying otherwise. A hosted run dispatched by our worker can go one step further — when its record lands, it carries the venue VM's Google-signed instance identity, checked at intake — but that names the machine, not the code it ran, and the result keeps saying which claim it is making.

Where this fits

th run is the step before claim submit. Run it until it is green, then submit. On a first-party posting you can read the poster's own verdict afterwards with claim runs <id>.