The status spec defines how Flow resolves the status of each repo in a workspace. Statuses are evaluated top-to-bottom — the first passing check wins. A workspace's overall status is the least-advanced repo (highest index in the spec).
~/.flow/status.yaml # Global default
~/.flow/workspaces/<workspace-id>/status.yaml # Workspace override (fully replaces global)
Flow ships with five statuses that model a standard PR-based workflow. These are created automatically on first run at ~/.flow/status.yaml. Run flow reset status to restore defaults after customizing.
| Status | Color | Condition | Dependencies |
|---|---|---|---|
closed |
dimmed red | A merged PR exists for the branch and no open PRs remain | gh, jq |
stale |
magenta | No commits on the branch in the last 14 days | git only |
in-review |
purple | A non-draft PR is open for the branch | gh, jq |
in-progress |
yellow | Uncommitted changes, local commits ahead of remote, or a draft PR | git, gh (fallback) |
open |
green | Default — none of the above conditions matched | none |
closed — Requires both conditions: (1) at least one merged PR exists on the branch, AND (2) no open PRs remain. This ensures the workspace isn't marked closed if follow-up PRs are still open.
stale — Compares the timestamp of the most recent commit on the branch against the current time. If the last commit is older than 14 days (1,209,600 seconds), the repo is stale. This is a local-only check that doesn't require GitHub access.
in-review — Checks for any open PR on the branch that is NOT a draft. If you've opened a PR and marked it ready for review, this status matches.
in-progress — Three conditions checked in order (first match wins):
git status --porcelain— uncommitted or unstaged changes in the worktreegit log origin/$BRANCH..HEAD— local commits that haven't been pushedgh pr listwith draft filter — a draft PR exists on the branch
If gh is not installed, checks 1 and 2 still work. The draft PR check gracefully fails (exits non-zero), falling through to the next status.
open — The default fallback. If no check matched, the repo has no detectable work. This typically means the workspace was just created or the branch matches the remote exactly.
Tip
The check commands are designed to be generated by AI, not written by hand. Describe the condition you want to detect in plain language, then let an agent produce the shell one-liner. Iterate until the check does what you need, then paste it into the spec. The description field is for humans; the check field is for machines.
apiVersion: flow/v1
kind: Status
spec:
statuses:
- name: closed
description: Merged PR, no open PRs remaining
color: "131"
check: >-
gh pr list --repo "$FLOW_REPO_SLUG" --head "$FLOW_REPO_BRANCH" --state merged --json number
| jq -e 'length > 0' > /dev/null 2>&1
&& gh pr list --repo "$FLOW_REPO_SLUG" --head "$FLOW_REPO_BRANCH" --state open --json number
| jq -e 'length == 0' > /dev/null 2>&1
- name: stale
description: No commits on branch in last 14 days
color: magenta
check: >-
_now=$(date +%s)
&& _last=$(git -C "$FLOW_REPO_PATH" log -1 --format=%ct 2>/dev/null)
&& [ -n "$_last" ]
&& [ $((_now - _last)) -gt 1209600 ]
- name: in-review
description: Non-draft open PR on branch
color: purple
check: >-
gh pr list --repo "$FLOW_REPO_SLUG" --head "$FLOW_REPO_BRANCH" --state open --json isDraft
| jq -e 'map(select(.isDraft == false)) | length > 0' > /dev/null 2>&1
- name: in-progress
description: Uncommitted changes, unpushed commits, or draft PR
color: yellow
check: >-
git -C "$FLOW_REPO_PATH" status --porcelain 2>/dev/null | grep -q .
|| git -C "$FLOW_REPO_PATH" log --oneline "origin/$FLOW_REPO_BRANCH..HEAD" 2>/dev/null | grep -q .
|| gh pr list --repo "$FLOW_REPO_SLUG" --head "$FLOW_REPO_BRANCH" --state open --json isDraft
| jq -e 'map(select(.isDraft)) | length > 0' > /dev/null 2>&1
- name: open
description: No changes detected
color: green
default: true| Field | Required | Description |
|---|---|---|
apiVersion |
Yes | Must be flow/v1 |
kind |
Yes | Must be Status |
spec.statuses[] |
Yes | Must contain at least one entry |
spec.statuses[].name |
Yes | Unique status name |
spec.statuses[].description |
No | Human-readable description |
spec.statuses[].color |
No | Display color — named (green, yellow, purple, magenta, cyan, red, blue, gray) or raw ANSI 256 code (e.g. "131") |
spec.statuses[].check |
Yes* | Shell command evaluated via sh -c (*not required for the default entry) |
spec.statuses[].default |
No | Exactly one entry must have default: true |
Each check command receives the following environment variables:
| Variable | Description |
|---|---|
FLOW_REPO_URL |
Repo URL from the state file |
FLOW_REPO_BRANCH |
Branch name from the state file |
FLOW_REPO_PATH |
Absolute path to the worktree directory |
FLOW_REPO_SLUG |
Repo in owner/repo format (derived from URL, works with gh --repo) |
FLOW_WORKSPACE_ID |
Workspace directory ID |
FLOW_WORKSPACE_NAME |
Workspace display name |
- Checks run top-to-bottom per repo. The first check that exits
0determines that repo's status. - If no check passes, the default status is used.
- A workspace's overall status is the least-advanced repo — the one with the highest index in the statuses list.
- If a workspace has its own
status.yaml, it fully replaces the global spec (no merging).
The status table sorts workspaces by status group, then by creation date (most recent first) within each group. The display order is the reverse of the spec order — the default status (bottom of spec) appears at the top of the table, and the most-advanced status (top of spec) appears at the bottom.
The check commands are shell one-liners. They aren't meant to be human-readable — they're generated by AI agents and only need to exit 0 or non-zero. The description field is what matters for understanding intent; the check field is the machine-evaluated implementation.
You can add, remove, reorder, or rename statuses. The only requirements are:
- Exactly one entry must have
default: true - Non-default entries must have a
checkcommand - Status names must be unique
Checks that depend on gh will gracefully degrade — if gh is not installed, those checks exit non-zero and fall through to the next status. Local-only checks (git, date) work without any external tools.