LocalCI
Run your project's existing checks on your computer, keep the output in your terminal, and report the verdict against the Git commit the checks ran on. LocalCI can show a passing or failing status on GitHub pull requests. Reporting is optional and off by default: with no reporter, a run is a local check that keeps everything on your machine.
LocalCI works with any language and any check command. Your code can stay on GitHub or another Git host. You do not need a GitTrove-hosted repository, a GitTrove framework, a hosted runner, or a workflow file.
Before you start
You need:
- Git and a repository with at least one commit.
- Python 3.13 or 3.14 to run LocalCI. Your project's language and Python version can be different when LocalCI is installed as an isolated tool.
- A working command that runs your project's checks, with its tools and dependencies already installed. LocalCI does not prepare your project environment.
- A LocalCI subscription and a dashboard key to install the package. The key authenticates the package download; running LocalCI needs no GitTrove key.
LocalCI runs commands from the repository root, even when you invoke
it from a subdirectory. It uses your current environment and
PATH. It does not sandbox commands, so run it only on
checkouts you trust.
Install
Visit the LocalCI listing for the current price, subscribe, and create a key in your GitTrove dashboard. The key is shown once.
Install uv if you do not already use it.
Store the key in your secret manager. For example, with gopass, run
gopass insert gittrove/localciand paste the key at its prompt. Then install:UV_DEFAULT_INDEX="https://__token__:$(gopass show -o gittrove/localci)@pypi.gittrove.com/simple/" \ uv tool install --python 3.13 LocalCI localci --version
The examples use a POSIX shell, such as Bash or Zsh. With a different
secret manager or shell, substitute your own retrieval command for
$(gopass show -o gittrove/localci). Keep keys out of
repository files and command history. You do not need to use gopass to
run LocalCI.
uv tool install gives the command its own environment.
If localci is not found afterward, run
uv tool update-shell, reopen your terminal, and try again.
If you already manage a Python 3.13 or 3.14 virtual environment, you can
instead run this in that environment:
PIP_INDEX_URL="https://__token__:$(gopass show -o gittrove/localci)@pypi.gittrove.com/simple/" \
python -m pip install LocalCIInstaller credentials are supplied through environment variables rather than command-line arguments. LocalCI has no runtime package dependencies.
Your first run
Choose the command you already use to check your project. In the
repository root, add a [tool.localci] table to
pyproject.toml. If your project does not have this file,
create it with just this table. This works for non-Python projects
too.
For a Python project managed by uv:
[tool.localci]
command = ["uv", "run", "--locked", "python", "-m", "pytest"]Other examples:
| Your existing check command | Configuration |
|---|---|
npm test |
command = ["npm", "test"] |
make check |
command = ["make", "check"] |
cargo test --locked |
command = ["cargo", "test", "--locked"] |
just check |
command = ["just", "check"] (the default) |
Each element is one argument. Commands are not interpreted by a
shell, so &&, pipes, wildcards, and
$VARIABLE are not expanded. For multiple checks, configure
one script or task that runs all of them and exits nonzero if any
fails.
Run the checks:
localciLocalCI: acme/widget at 0632064b651f
LocalCI: running npm test
...
LocalCI: npm test passed
LocalCI gets its owner/name from origin, or
from the sole remote if there is only one. For a local repository
without a remote, or a host with nested groups, set
repository = "acme/widget" explicitly.
Nothing is sent anywhere by that run. A working-tree run checks the files as they stand, and the closing line says so when there are uncommitted changes. To execute the checks without acting on the result, and without any credential:
localci --dry-runWith GitHub reporting enabled, the dry run prints the status payload instead of posting it.
Committed profiles and retained evidence
Use localci validate for ordered checks from a committed
snapshot, with optional pinned Docker execution and retained
logs/artifacts. Use localci report to publish verified
execution evidence later. The execution
guide shows configuration, overrides, failure behavior, and the
versioned record. These commands do not manage application previews or
deployment policy.
Adopt LocalCI in an existing project
An existing project needs no new checks, no workflow file, and no change to how it installs its dependencies. Point LocalCI at the command the project already runs, then verify it:
Follow LocalCI for coding agents
for the complete installation, configuration, and verification contract.
By default, install in isolation with an authorized existing credential,
preserve configuration, run full existing checks once with
localci --no-github, verify failure propagation, and leave
a project instruction. GitHub reporting and committed execution profiles
are opt-in. Report failing checks separately from setup failures and
follow the project's policy for commits and pushes.
Show results on GitHub pull requests
Follow the GitHub setup guide. In brief:
- Sign in with
gh auth login, or setGITHUB_TOKENto a token permitted to write commit statuses on the repository. - Add
github = trueto your existing[tool.localci]table. - Commit all changes, push the commit, and run
localci.
LocalCI: acme/widget at 0632064b651f
LocalCI: running npm test
...
LocalCI: reported success to GitHub as 'LocalCI'
GitHub reporting requires a clean working tree, including no
untracked files. Checks that change tracked files or create unignored
files cannot publish a status. Ignore disposable test artifacts, and use
check modes rather than formatters that rewrite source. If
HEAD changes while checks run, LocalCI publishes nothing.
--sha can assert the expected HEAD; it cannot
relabel a run as a different commit.
To check work in progress, use localci --no-github, or
localci --no-github --dry-run to keep it entirely
local.
A pending status while the checks run
GitHub shows one status per context, so a verdict only appears when a
run finishes. To make a run visible while it is happening, and to
replace an earlier green status as soon as a new run starts, add
github_pending = true:
[tool.localci]
command = ["npm", "test"]
github = true
github_pending = trueLocalCI then posts pending before the checks start and
the verdict when they finish. A run that cannot start, or times out,
closes the pending status with an error instead, so nothing
is left waiting. Interrupting with Ctrl-C publishes nothing, so a
pending status can stay until another run replaces it.
This is off by default because pending is a real gate: a required
status that is published and never replaced blocks the pull request. It
is also the one status that needs reporting, so
github_pending with github = false is a
configuration error rather than a silent no-op.
Stopping a check that hangs
A check that waits on a prompt or a network call has no deadline unless you set one:
[tool.localci]
command = ["npm", "test"]
timeout_seconds = 900LocalCI stops the command and everything it started after that many
seconds, gives it a few seconds to exit, then forces whatever is left.
The run reports timed out after 900s, exits 1, and never
posts success. --timeout SECONDS overrides the setting for
one run, and --timeout 0 removes the deadline.
Termination reaches the command and the processes it started on macOS and Linux, where each run gets its own process group. On Windows the command gets its own group as well, but a signal reaches only the command itself, so a process it started can survive a timeout or an interrupt. LocalCI cannot prove that a container or a service the command launched somewhere else has stopped either; a project-owned Docker command still owns its own cleanup. Because the command runs in its own session it does not receive the terminal's own Ctrl-C; LocalCI stops it and reports the interruption itself.
A GitHub status is a report from a developer's machine. It does not independently verify the developer's environment or prevent someone with status-write access from posting another result. Keep hosted CI for any independent validation your team requires. Avoid editing files concurrently with a run; LocalCI checks the checkout before and after execution, not every filesystem change in between.
Configuration and options
All configuration lives in the root pyproject.toml.
Unknown option names are errors, so a typo cannot silently select the
defaults.
| Setting | Default | Purpose |
|---|---|---|
command |
["just", "check"] |
Executable and arguments for your checks. |
repository |
inferred from remote | Repository identifier as owner/name. |
github |
false |
Post a GitHub commit status for the verdict. |
github_context |
"LocalCI" |
GitHub status name, including for branch rules. |
github_pending |
false |
Post a pending status before the checks run. Needs
github. |
timeout_seconds |
unset (no deadline) | Stop the checks after this many seconds. |
github_url |
"https://api.github.com" |
Advanced: GitHub Enterprise API base URL. |
Credentials come from the environment, never the configuration table. Only use API URLs you trust; LocalCI sends the corresponding token to the configured URL. Use HTTPS for remote services.
| CLI option | Purpose |
|---|---|
--repository OWNER/NAME |
Override the configured or inferred identifier. |
--sha SHA |
Require that HEAD equals this full 40-character commit
SHA. |
--command ARGUMENT |
Replace the command, repeating the flag for each argument. Use
--command=-q for an argument starting with
-. |
--timeout SECONDS |
Stop the checks after this many seconds. 0 means no
deadline, which overrides a configured
timeout_seconds. |
--github / --no-github |
Enable or disable GitHub reporting for this run. |
--github-pending /
--no-github-pending |
Enable or disable the pending status for this run. |
--dry-run |
Execute checks and print the payload without API requests or credentials. |
--help / --version |
Show help or the installed version. |
Failures and exit codes
| Code | Meaning and next step |
|---|---|
| 0 | Checks passed, and GitHub accepted the status when reporting was enabled. |
| 1 | Checks failed or timed out. LocalCI still attempts to report the failure unless this is a dry run. Read any reporting errors too. |
| 2 | Configuration or startup failed. Fix the displayed problem and rerun. |
| 3 | No trustworthy result could be published, or the GitHub status failed after the checks passed. Read the error to see what is missing. |
| 130 | Checks were interrupted with Ctrl-C. No result was published. |
A check outcome always wins over a reporting problem: a run whose checks failed or timed out exits 1 even when its status could not be posted.
Common fixes:
justis not installed: configure your project's actual command above, or installjustif that is the command your project uses.- A project tool or import is missing: activate or prepare the project's environment as you would when running its checks by hand. LocalCI's isolated environment does not contain your project's dependencies.
- No remote or no commit: create a commit and
configure a remote, or use
--repository owner/namewhen only the remote is missing. - GitHub reporting is refused: sign in with
ghfor the correct host, or setGITHUB_TOKEN. See the GitHub setup guide. - Installation is refused: check that the key is valid and that the account has access to LocalCI. Use the account that owns your subscription.
- A status needs a clean tree: commit or stash
changes, including untracked files, then run again, or use
--no-githubto check a dirty tree locally. - Network error: the checks have already run, and the status was not posted. Restore connectivity and rerun. Results are not queued for automatic retry.
- A check hangs: set
timeout_seconds, or pass--timeout SECONDS, to stop it with a clear message instead of holding the terminal. Use--timeout 0to ignore a configured timeout for one run. - A status is stuck on pending: a run that published pending and then died cannot clear it, and GitHub keeps showing pending until another run replaces it. Run LocalCI again on the same commit, or post a status by hand if you must unblock the pull request sooner.
For help, email audrey@feldroy.com.
Include localci --version, your OS, your check command, and
the error with credentials removed. Never include keys or private check
output.
What leaves your machine
Without GitHub reporting, LocalCI sends nothing: the checks run on
your machine and the verdict is printed to your terminal. With GitHub
reporting enabled, LocalCI sends the repository identifier, full commit
SHA, state, status name, and a description containing the command
including its arguments to GitHub's commit-status API.
With github_pending enabled it sends the same fields once
at the start of the run, with the state pending.
Check output and source files are not uploaded by LocalCI. Do not put secrets in command arguments because those arguments appear in the posted description. Pass secrets through the environment or your secret manager instead. The checks LocalCI invokes may make their own network requests.
License
LocalCI is proprietary software owned by Feldroy, LLC. A paid
subscription grants the right to use it under the GitTrove terms of service, which
cover your account, the private index, and every package sold through
it. Those terms ship with LocalCI as the bundled LICENSE
notice. The price is 29 USDC every 30 days, collected from your wallet
until you cancel.