docs reference Configuration & files
17-configuration-and-files.md ReferenceJul 17, 2026

Configuration & files

The gs CLI keeps two kinds of state on disk: your logins, which live in your home directory and are shared across every repo, and per-repo bookkeeping, which lives in a .gitstack/ directory inside each clone. One repo file is tracked and travels with the repository: .gitstack/policy.toml, which sets what a review needs before it lands. Who can read, write, and own a repo is not a file; it is set on the repo's settings page (see Repository access). This page documents each file, its schema, and which commands write it.

~/.config/gs/config.toml#

Your default login. gs login writes it after a successful browser handshake, and every command that talks to the server reads it (unless a named profile is selected; see below). Absence means "logged out": commands that need auth print not logged in; run gs login first.

toml
server = "https://gitstack.sh"
token  = "gsd_9f3c...redacted"
handle = "alice"
Field Type Meaning
server string Normalized base URL of the GitStack endpoint this login talks to. Always https://gitstack.sh unless you logged in with gs login --server against a development build.
token string The device token minted during the handshake. Sent as a bearer token on every API call.
handle string The account handle this login belongs to. gs auth list shows it per profile.
default_profile string, optional Name of the profile that answers when nothing else selects one. Set by gs auth use <name>. Absent means the default login in this file answers.
[update] table, optional Cadence for the update nudge gs prints when the server advertises a newer release.
Field
server
Type
string
Meaning
Normalized base URL of the GitStack endpoint this login talks to. Always https://gitstack.sh unless you logged in with gs login --server against a development build.
Field
token
Type
string
Meaning
The device token minted during the handshake. Sent as a bearer token on every API call.
Field
handle
Type
string
Meaning
The account handle this login belongs to. gs auth list shows it per profile.
Field
default_profile
Type
string, optional
Meaning
Name of the profile that answers when nothing else selects one. Set by gs auth use <name>. Absent means the default login in this file answers.
Field
[update]
Type
table, optional
Meaning
Cadence for the update nudge gs prints when the server advertises a newer release.

The file is written with 0600 permissions inside ~/.config/gs/ (created 0700). gs whoami prints a summary of the active login without exposing the token.

gs logout revokes every device token you own on the server, then removes the selected profile's config.toml and token file. Other profiles keep their sessions. It is idempotent: running it while already logged out does nothing.

Auth profiles#

One machine can hold several signed-in identities, say a personal account and a work account on the same server, or accounts on two different servers. Each extra identity is a named profile:

bash
gs login --profile work

That writes ~/.config/gs/profiles/work/ with its own config.toml (server, token, handle) and token sidecar, in the same shapes described on this page. The plain gs login identity is the default profile and stays where it always was, directly in ~/.config/gs/.

Every command picks exactly one profile to act as, checking in order:

  1. The --profile <name> flag on the command itself.
  2. The GS_PROFILE environment variable.
  3. The repository pin, if the current repo has one.
  4. The global default: default_profile in config.toml, else the default login itself.

A selector that names a profile that does not exist is a hard error. gs never falls through to a different identity than the one you asked for.

Two commands manage the roster:

bash
gs auth list              # every profile: server, handle, default marker,
                          # and which one answers in this directory
gs auth use work          # set the global default
gs auth use work --repo   # pin the current repository instead

The repo pin is a one-line file at .git/gs-profile (in the git common directory, so every linked worktree of the checkout shares it). It is machine-local and never committed: your clone acts as work without imposing anything on anyone else who clones the repo.

The git credential helper#

gs login also sets up plain git push to authenticate against the same server, so pushes from inside a stack do not prompt for a password. Alongside config.toml it writes two more files into ~/.config/gs/:

File Mode Purpose
token 0600 The device token as a bare string. The helper reads it.
credential-helper.sh 0700 A short shell script that answers git's credential query with username gs and the token as the password. When gs runs git on your behalf it exports GS_PROFILE, so the helper answers with the active profile's token; outside gs it uses the default profile.
File
token
Mode
0600
Purpose
The device token as a bare string. The helper reads it.
File
credential-helper.sh
Mode
0700
Purpose
A short shell script that answers git's credential query with username gs and the token as the password. When gs runs git on your behalf it exports GS_PROFILE, so the helper answers with the active profile's token; outside gs it uses the default profile.

It then adds a global git config entry so git runs that helper for your server's URL only:

credential.https://gitstack.sh.helper = !/Users/you/.config/gs/credential-helper.sh

The entry is reset before it is added, which clears any inherited helper (such as the macOS keychain helper Xcode ships) for that one URL. Everything else in your git config is left alone.

What logout leaves behind

gs logout removes the selected profile's config.toml and token, which is enough to stop authenticating as that identity. It does not delete credential-helper.sh or the git config entry. Both stay but go inert once no profile has a token left to read. A later gs login reuses them.

The .gitstack/ directory#

Every clone has a .gitstack/ directory at its root holding gs's per-repo bookkeeping. gs creates it on first use and adds two lines to the repo's machine-local ignore file, .git/info/exclude: /.gitstack/* and !/.gitstack/*.toml. The bookkeeping never shows up in git status, never lands in a commit, and is never imposed on anyone else who clones the repo. The exclude lines are written once and shared across every worktree of the clone.

The same directory holds the repo's committed config: policy.toml and checks.toml. The *.toml exception keeps those visible, so you change them like any other file: edit, gs add .gitstack/policy.toml, commit, and land the review. gs add stages config files even in clones that ignore the whole directory.

Do not hand-edit the bookkeeping files

Everything in .gitstack/ other than the .toml config is gs-owned state. gs reads and rewrites it between commands, and the manifest is hashed and pushed verbatim, so a manual edit either gets clobbered or desynchronizes your local view from the server.

The directory holds the manifest, a couple of navigation and autostack sidecars, and a set of short-lived files that carry state from one command to the next:

File Written by Read by
branch-reviews.json most review verbs everything that needs the stack shape
last-branch gs checkout when it detaches gs checkout on the way back
autostack-fixes.json gs autostack inspection after a gated autostack run
pending-force.json gs commit / gs amend after a non-fast-forward rewrite gs push (then cleared)
pending-resolves.json commits carrying --resolves gs push (then cleared)
push-rejected.json gs push on a rejected push reconciliation gates (then cleared)
File
branch-reviews.json
Written by
most review verbs
Read by
everything that needs the stack shape
File
last-branch
Written by
gs checkout when it detaches
Read by
gs checkout on the way back
File
autostack-fixes.json
Written by
gs autostack
Read by
inspection after a gated autostack run
File
pending-force.json
Written by
gs commit / gs amend after a non-fast-forward rewrite
Read by
gs push (then cleared)
File
pending-resolves.json
Written by
commits carrying --resolves
Read by
gs push (then cleared)
File
push-rejected.json
Written by
gs push on a rejected push
Read by
reconciliation gates (then cleared)

.gitstack/branch-reviews.json#

The manifest. It maps the current branch to its tip commit and lists, bottom to top, the reviews carved out of the branch with each review's tip SHA. gs pushes this file to the server as a git blob, so the on-disk bytes and the pushed blob hash to the same SHA when they match.

json
{
  "branch": "add-billing",
  "branch_tip": "8f2a1c9e4b7d0a6f3c5e8b1d2f4a7c9e0b3d6f8a",
  "reviews": [
    { "review_id": "R412", "tip_sha": "1a2b3c4d5e6f7081920a1b2c3d4e5f6071829304" },
    { "review_id": "R413", "tip_sha": "8f2a1c9e4b7d0a6f3c5e8b1d2f4a7c9e0b3d6f8a" }
  ],
  "fixes": [188, 190]
}
Field Type Meaning
branch string Branch this manifest describes. A manifest loaded on a different branch is refused as stale.
branch_tip 40-char SHA Current tip of the branch. Must equal the last review's tip_sha.
reviews array Reviews in stack order, bottom first. Each row is a review_id (like R412) plus its tip_sha.
fixes array of ints Issue numbers this stack closes. The link is branch-scoped: the server closes each issue only once every review on the branch has landed or been abandoned, never when a single review lands.
Field
branch
Type
string
Meaning
Branch this manifest describes. A manifest loaded on a different branch is refused as stale.
Field
branch_tip
Type
40-char SHA
Meaning
Current tip of the branch. Must equal the last review's tip_sha.
Field
reviews
Type
array
Meaning
Reviews in stack order, bottom first. Each row is a review_id (like R412) plus its tip_sha.
Field
fixes
Type
array of ints
Meaning
Issue numbers this stack closes. The link is branch-scoped: the server closes each issue only once every review on the branch has landed or been abandoned, never when a single review lands.

A resolves field also exists on the wire to carry drained comment-thread ids to the server on push, but it is stripped from the persistent on-disk form.

For how reviews and stacks map onto this file, see Stacks & navigation and Reviews.

.gitstack/last-branch#

A one-line file naming the branch you were on. gs checkout r5 detaches HEAD at a review's tip so you can inspect it; before detaching, gs records your branch here. Switching back to a real branch clears the file. That is how gs knows which branch to return you to after you finish looking at a detached review.

.gitstack/autostack-fixes.json#

A sidecar written by gs autostack when its gate applies auto-fixes on top of your reorganized work. It records what the gate flagged and the exact diff the fixes introduced, kept separate from "your work, reorganized" so you can review the machine's changes on their own.

json
{
  "flags": [ /* findings the gate raised */ ],
  "gate_delta": "diff --git a/... (unified diff of the auto-fixes)"
}

Repository access#

Who can read, write, and own a repo is set on the repo's settings page, under Access. It is not a file in the repository, so moving a branch never changes who has access.

Role Grants
owner Full control: manage access, land anyone's review, administer the repo.
write Push branches and land their own reviews.
read Read-only access.
Role
owner
Grants
Full control: manage access, land anyone's review, administer the repo.
Role
write
Grants
Push branches and land their own reviews.
Role
read
Grants
Read-only access.

The person who created the repo is always an owner and is not listed. In an organization, members reach every repo through their org role as well. Repo owners, org owners and admins can add someone by handle, change a role, or remove someone. A change takes effect right away and is recorded in the audit log.

Repos created with an older GitStack may still have a .gitstack/owners.toml. GitStack no longer reads it, and the settings page points it out so you can delete it.

.gitstack/policy.toml#

The repo's landing policy: which CI checks must pass and who must approve a review before it lands, and what happens when a land turns the default branch red. It also lists branches gs repo prune must keep. It is tracked, lives on the default branch, and changes only through a review that a repo owner approves. The policy that applies to a land is the one already on the default branch, never the one in the review being landed.

toml
# .gitstack/policy.toml
[checks]
enabled = true
required = ["build-and-test", "lint"]

[approvals]
min_lgtms = 2
require_requested_reviewers = true
owners_bypass = false

[[approvals.rules]]
paths = ["docs/**", "CHANGELOG.md"]
min_lgtms = 0

[[approvals.rules]]
paths = ["src/billing/**"]
min_lgtms = 3
owner_lgtm = true

[prune]
keep = ["staging", "release/*"]

[mirror]
github_managed = ["production"]

[landing]
on_red = "revert"

Every table is optional. Without the file, a review needs no LGTM, owners may land anything, and no checks are required. A change to an owner-controlled file still needs an owner's LGTM.

[approvals]:

Key Type Default Meaning
min_lgtms integer, 0 to 10 0 Active LGTMs a review needs from writers or owners other than its author. 0 lets a writer land their own review with no LGTM.
require_requested_reviewers bool false Every requested reviewer who can approve (a writer or owner other than the author) must LGTM. Readers are skipped.
owners_bypass bool true Repo owners may land without meeting the rest of the policy. Set it to false and owners need the same approvals as everyone, automerge included.
Key
min_lgtms
Type
integer, 0 to 10
Default
0
Meaning
Active LGTMs a review needs from writers or owners other than its author. 0 lets a writer land their own review with no LGTM.
Key
require_requested_reviewers
Type
bool
Default
false
Meaning
Every requested reviewer who can approve (a writer or owner other than the author) must LGTM. Readers are skipped.
Key
owners_bypass
Type
bool
Default
true
Meaning
Repo owners may land without meeting the rest of the policy. Set it to false and owners need the same approvals as everyone, automerge included.

Site admins can always land, so a repo can't lock itself out. Automerge never gets the owner bypass.

[[approvals.rules]] set the approvals for the files they match, so review effort goes where the risk is:

Key Type Meaning
paths array of globs, required The files the rule covers. Repo-relative, no leading /. * stays within one directory; ** as a whole segment matches any depth, so docs/** covers everything under docs/.
min_lgtms integer, 0 to 10, required LGTMs a review changing a matched file needs, in place of the top-level min_lgtms.
owner_lgtm bool, default false A review changing a matched file also needs a repo owner's LGTM.
Key
paths
Type
array of globs, required
Meaning
The files the rule covers. Repo-relative, no leading /. * stays within one directory; ** as a whole segment matches any depth, so docs/** covers everything under docs/.
Key
min_lgtms
Type
integer, 0 to 10, required
Meaning
LGTMs a review changing a matched file needs, in place of the top-level min_lgtms.
Key
owner_lgtm
Type
bool, default false
Meaning
A review changing a matched file also needs a repo owner's LGTM.

Each file a review changes takes the last rule that matches it, as in CODEOWNERS; files no rule matches take the top-level min_lgtms. The review needs the most any of its files needs, counting only the review's own changes, not the rest of its stack. gs stack, gs submit and the review page name the rule that decided: needs 1 more LGTM (1 of 3, src/billing/**). If GitStack can't list a review's files, it applies the strictest rule.

[checks]:

Key Type Meaning
enabled bool Turn the required-checks gate on or off. On a repo that mirrors to GitHub it defaults to on once required names a check.
required array of strings Check names that must pass on the exact commit being landed. Names match the check runs GitHub reports.
dispatch string A GitHub Actions workflow file, like "ci.yml", that GitStack starts on a review branch when the required checks have not run on its tip: at land time (gs submit waits and lands once they pass), from automerge, or on request with gs checks run. The workflow needs a workflow_dispatch trigger, and the GitHub App needs the Actions read and write permission. Empty (the default) leaves CI to GitHub's own triggers.
Key
enabled
Type
bool
Meaning
Turn the required-checks gate on or off. On a repo that mirrors to GitHub it defaults to on once required names a check.
Key
required
Type
array of strings
Meaning
Check names that must pass on the exact commit being landed. Names match the check runs GitHub reports.
Key
dispatch
Type
string
Meaning
A GitHub Actions workflow file, like "ci.yml", that GitStack starts on a review branch when the required checks have not run on its tip: at land time (gs submit waits and lands once they pass), from automerge, or on request with gs checks run. The workflow needs a workflow_dispatch trigger, and the GitHub App needs the Actions read and write permission. Empty (the default) leaves CI to GitHub's own triggers.

[prune]:

Key Type Meaning
keep array of globs Branches gs repo prune never deletes, whoever runs it. * does not cross a /, so release/* keeps release/1.2 but not release/1/hot.
Key
keep
Type
array of globs
Meaning
Branches gs repo prune never deletes, whoever runs it. * does not cross a /, so release/* keeps release/1.2 but not release/1/hot.

[mirror]:

Key Type Meaning
github_managed array of globs Branches GitHub owns, such as a deploy branch a script pushes to GitHub directly. GitStack never pushes them, always takes GitHub's version (rollbacks included), refuses pushes and lands into them, and never prunes or deletes them. Can't match the default branch.
Key
github_managed
Type
array of globs
Meaning
Branches GitHub owns, such as a deploy branch a script pushes to GitHub directly. GitStack never pushes them, always takes GitHub's version (rollbacks included), refuses pushes and lands into them, and never prunes or deletes them. Can't match the default branch.

[landing]:

Key Type Default Meaning
on_red "revert" or "notify" "revert" What GitStack does when a land turns a required check red on the branch it landed into. "revert" reverts the land that broke it and lands the revert; "notify" only comments on the reviews. See When a land breaks the branch.
Key
on_red
Type
"revert" or "notify"
Default
"revert"
Meaning
What GitStack does when a land turns a required check red on the branch it landed into. "revert" reverts the land that broke it and lands the revert; "notify" only comments on the reviews. See When a land breaks the branch.

An invalid file counts as "notify".

The file is read strictly. A typo like min_lgtm, a wrong type, a min_lgtms outside 0 to 10, a path rule missing paths or min_lgtms, a malformed glob, an on_red other than "revert" or "notify", or a github_managed glob that matches the default branch makes it invalid, and an invalid policy refuses every land except a repo owner's, so an owner can land the fix. It also refuses every prune, since the branches it means to keep are unknown. The repo settings page shows the policy in force, and flags an invalid file.

Repo owners can also change the policy from the repo settings page. Like a collaborator change, saving does not touch the default branch: it opens (or updates) a review on the reserved settings/policy branch that edits policy.toml for you, and the new rules take effect when a repo owner lands it.

Migrating from checks.toml#

Repos that set required checks before policy.toml existed have a .gitstack/checks.toml with enabled and required at the top level. It keeps working, and migrating is optional and manual:

  1. In a review, add a [checks] table to .gitstack/policy.toml with the same enabled and required keys, and have a repo owner approve and land it.
  2. Once it lands, policy.toml wins and checks.toml is ignored. The repo settings page says so. Delete checks.toml in a later review.

A partial migration is safe. A policy.toml with only an [approvals] table, or with an empty [checks] table, leaves checks.toml in force.