Skip to content
This repository was archived by the owner on Sep 27, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@ flowchart LR
again on resume. Every commit carries a `Greentree-Change-Id` trailer,
the stable identity that will let stacks of verified changes survive
rebases (see the [roadmap](docs/ROADMAP.md)).
4. **Statuses.** With a GitHub token in the environment, a pushed publish
posts a `greentree/<check>` commit status on the new SHA. Statuses
satisfy branch protection required checks.
4. **Statuses.** With a GitHub token available (an env var, or `gh auth
token` if you're signed in with the `gh` CLI), a pushed publish posts a
`greentree/<check>` commit status on the new SHA. Statuses satisfy
branch protection required checks.

## Install

Expand Down Expand Up @@ -147,10 +148,11 @@ Every command takes `--json` (exactly one JSON object on stdout) and
| 0 | success: checks green, published, or no-op |
| 10 | a check ran and failed |
| 11 | publish refused: tree not verified |
| 12 | unsnapshotable state: mid-merge, conflicted index, dirty submodule |
| 12 | repository state blocks it: conflicted index, dirty submodule, or publishing mid-rebase |
| 13 | another greentree process holds the lock |
| 14 | configuration error |
| 15 | publish machinery failed: CAS refused, push rejected, no remote |
| 16 | free disk below `min_free_disk`: check refused, or aborted mid-run |

Full JSON shapes and the verdict record schema are in
[docs/SPEC.md](docs/SPEC.md).
Expand All @@ -160,6 +162,8 @@ Full JSON shapes and the verdict record schema are in
```yaml
version: 1

min_free_disk: 10G # floor for every check; default 5G, "0" disables

checks:
quick:
run: pnpm lint && pnpm test
Expand All @@ -169,6 +173,8 @@ checks:
required_for_publish: true
fresh: 30m # a pass older than this will not satisfy the gate
timeout: 20m # default 15m; SIGTERM, 5s grace, SIGKILL
min_free_disk: 60G # this check's own floor: refuse to start below it,
# and kill it if free space falls under it mid-run

snapshot:
exclude: ["docs/generated/**"] # paths checks may write without
Expand Down Expand Up @@ -200,6 +206,10 @@ freely with a running watcher.
- Checks run under `/bin/sh -c` in their own process group with `GIT_*`
redirection variables scrubbed: a `git` command inside your test suite
sees your real repo, never greentree's shadow index.
- Checks are bounded in time and in disk: below `min_free_disk` free space
a check is refused before it starts (exit 16), and one that eats through
the floor mid-run is killed like a timeout, so a build cannot fill the
machine it runs on.
- greentree never writes your index, your worktree, or your refs outside
`refs/greentree/*`, except at publish, where the ref move is a
compare-and-swap and the push records its lease explicitly.
Expand Down Expand Up @@ -259,11 +269,11 @@ A commit that never went through a workspace has no status and cannot
merge. To get green, route it through verification. That is the whole
enforcement, with zero CI minutes.

greentree posts statuses with a GitHub token from the environment
(`GREENTREE_GITHUB_TOKEN` or `GITHUB_TOKEN`). Checks never see it: the
token is scrubbed from every check subprocess and reaches only the status
API call. Use a fine-grained token scoped to the repo with "Commit
statuses: write".
greentree posts statuses with a GitHub token resolved from
`GREENTREE_GITHUB_TOKEN`, then `GITHUB_TOKEN`, then (if neither is set and
`gh` is on PATH) `gh auth token`. Checks never see it: the token is
scrubbed from every check subprocess and reaches only the status API call.
Use a fine-grained token scoped to the repo with "Commit statuses: write".

Verification runs wherever you invoke greentree. Pointing it at a
persistent warm workspace instead of the local machine, so the cache and
Expand Down
27 changes: 24 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,36 @@ on a machine that holds credentials or persistent state.

## Credentials

`attest` and `publish --push` post commit statuses with a GitHub token
read from `GREENTREE_GITHUB_TOKEN` or `GITHUB_TOKEN`. Two guarantees:
`attest` and `publish --push` post commit statuses with a GitHub token,
resolved in order:

1. `GREENTREE_GITHUB_TOKEN` env
2. `GITHUB_TOKEN` env
3. `gh auth token --hostname github.com`, if a `gh` binary is on PATH

An explicit env var always wins over `gh`, so it stays the way to pin or
override the token used. The `gh` fallback exists so a box where you have
already run `gh auth login` just works, without the token being pasted into
a shell profile, an exported environment variable, or your shell history.

Where `gh` keeps that credential is `gh`'s business, and it varies: a
keyring where one is available, and a **plaintext `hosts.yml`** under
`~/.config/gh/` on a headless Linux box with no keyring — so treat the token
as on-disk unless you know otherwise. What using the fallback does buy is
that greentree never asks you to copy the token anywhere else, and never
puts it on a command line. Any failure resolving through `gh` (not
installed, not logged in, times out) falls through silently to "no token",
the same as before this chain existed.

Two guarantees:

- The token is scrubbed from every check subprocess, alongside the `GIT_*`
redirection variables. A `run:` command cannot read it. The token
reaches only the status API call.
- Use a fine-grained token scoped to the single repository with "Commit
statuses: write" and nothing else. That is the minimum blast radius if a
run is ever compromised. A classic PAT needs `repo:status`.
run is ever compromised. A classic PAT needs `repo:status`. This applies
whichever of the three sources supplies the token.

## What a green status means

Expand Down
3 changes: 2 additions & 1 deletion docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ it twice in a row is a no-op. Branch on exit code, not on output text:
| 0 | published (or already published) | done |
| 10 | a check failed | read `log_tail` in the JSON, fix, rerun |
| 11 | tree changed after its last verification | rerun `gate` |
| 12 | repo mid-merge/rebase or dirty submodule | resolve, rerun |
| 12 | conflicted index or dirty submodule; or `gate` during a rebase | resolve conflicts (`git add`) / finish the rebase, rerun |
| 13 | another greentree run in flight | wait, rerun |
| 16 | free disk below `min_free_disk` | free space, then rerun |

While iterating, `greentree test --json` gives the same verdicts without
publishing. `greentree status --json` answers "would publish succeed?"
Expand Down
Loading