Cycle Time Setup#
Cycle time measures how long active work took on an issue, excluding backlog time (unlike lead time, which measures full elapsed duration from creation to close). The measurement depends on your configured strategy. For the metric definition and formula, see the Cycle Time reference.
Choosing a strategy#
There are two cycle time strategies. Choose based on your workflow.
| Your workflow | Recommended strategy | Why |
|---|---|---|
| Issues with lifecycle labels | issue | Measures real work time (label applied to closed); immutable timestamps |
| PRs close issues (most OSS repos) | pr | Measures PR review time (created to merged) |
| Issues only, no labels or PRs | issue | Lead time works immediately; add an in-progress label for cycle time |
The PR strategy requires zero setup; the issue strategy gives richer data but requires label discipline.
No labels yet? The PR strategy works without any setup -- it uses PR merge times. Switch to the issue strategy later when you add lifecycle labels.
The PR strategy#
The PR strategy uses the closing PR's creation date as the cycle start and its merge date as the end. No extra configuration needed:
cycle_time:
strategy: prPRs must reference issues with "Closes #N" or "Fixes #N" in the description, or use GitHub's sidebar "Development" section. The PR does not need to be merged or out of draft -- opening a draft PR that mentions an issue is enough for a cycle time signal.
Lead time is unaffected by strategy choice -- it always measures issue creation to close.
The issue strategy#
The issue strategy uses labels as the cycle time signal. When a matching label is applied, that timestamp becomes the cycle start. The issue's close date is the cycle end.
Why labels#
Label event timestamps (LABELED_EVENT.createdAt) are immutable. Once applied, the timestamp never changes -- not on removal, not on re-application, not on any other event. This makes labels the only reliable "when did work start?" signal from the GitHub API.
Configuring lifecycle labels#
Tell the tool which labels mark "work started":
cycle_time:
strategy: issue
lifecycle:
in-progress:
match: ["label:in-progress"]Suggested labels:
in-progress(required for cycle time) -- apply when work startsin-review(optional) -- apply when a PR is opened for reviewdone(optional) -- apply when work is complete
To enable this:
- Create a label like
in-progressin your repo - Add
lifecycle.in-progress.matchto your config with the label name - Apply the label to issues when work starts
Or run preflight to auto-detect existing labels:
gh velocity config preflight -R owner/repo --writeIf you use a project board#
If your team uses a GitHub Projects v2 board, use gh-project-label-sync to apply lifecycle labels automatically when cards move. The board drives your workflow; labels provide the immutable timestamps gh-velocity needs.
The project board remains useful for velocity iteration/effort reads (via velocity.iteration.strategy: project-field and field: matchers), but is not used as a lifecycle or cycle-time signal.
Workflow patterns#
Solo developer / OSS workflow (PR strategy)#
Create an issue, open a PR with "Closes #N", merge, tag a release. Use cycle_time.strategy: pr. Works with no extra config.
cycle_time:
strategy: prTeam workflow with labels (issue strategy)#
Create an issue, apply in-progress label when work starts, open a PR, review, merge, release. Use cycle_time.strategy: issue with lifecycle.in-progress.match.
cycle_time:
strategy: issue
lifecycle:
in-progress:
match: ["label:in-progress"]
in-review:
match: ["label:in-review"]
done:
query: "is:closed"
match: ["label:done"]If you also use a project board for visibility, use gh-project-label-sync to automate the label step when moving cards on the board.
Team workflow without labels (PR strategy)#
Create an issue, developer opens a PR with "Closes #N", review, merge, release. Use cycle_time.strategy: pr. The PR creation date is the cycle start.
cycle_time:
strategy: prConnecting PRs to issues#
The tool finds PR-to-issue connections through GitHub's timeline events. A PR becomes a cycle time signal when it references an issue in any of these ways:
- Write
Fixes #42,Closes #42, orResolves #42in a PR description - Use GitHub's sidebar "Development" section to link a PR to an issue
- Mention
#42anywhere in the PR (creates a cross-reference event) - Any variation:
fix #42,close #42,resolve #42(case-insensitive)
None of the following are required:
- Special labels or tags
- A specific branch naming convention
- Webhooks or integrations
- A commit message format (unless you want commit-based enrichment)
Running cycle time commands#
Single issue:
gh velocity flow cycle-time 42
gh velocity flow cycle-time 42 -R cli/cliSingle PR (always uses PR created to merged, regardless of configured strategy):
gh velocity flow cycle-time --pr 99Bulk (all issues closed in a window):
gh velocity flow cycle-time --since 30d
gh velocity flow cycle-time --since 2026-01-01 --until 2026-02-01 --results jsonCycle time does not require a local clone -- it uses GitHub API signals (PR creation date, label events). Running from a local checkout adds commit counts and a fallback signal from commit history.
Troubleshooting cycle time#
If cycle time shows N/A for all or most issues, see Troubleshooting: Cycle time shows N/A.
Next steps#
- Labels as Lifecycle Signal -- why labels are the sole lifecycle signal
- Setting Up Flow Velocity -- configure effort-weighted iteration metrics
- Configuration Reference -- all cycle_time and lifecycle fields