Understanding the gh-stack Stack File Format and .git/gh-stack Management
The gh-stack CLI stores stacked branch metadata in a JSON file at .git/gh-stack, governed by a strict schema defined in internal/stack/schema.json and managed through atomic load/save operations with file locking in internal/stack/stack.go.
The github/gh-stack extension tracks stacked pull requests using a structured metadata file. This gh-stack stack file format persists branch relationships, commit SHAs, and pull request states between CLI invocations. The file lives in the repository's .git directory and follows a versioned JSON schema to ensure compatibility across different gh-stack versions.
Core Structure of the Stack File
The .git/gh-stack file conforms to the JSON Schema defined in internal/stack/schema.json. The Go implementation in internal/stack/stack.go unmarshals this data into strict types that enforce the schema.
Root Level Fields
| Field | Go Type | Description |
|---|---|---|
schemaVersion |
int |
Must be 1. Future versions cause older binaries to abort with a version mismatch error. |
repository |
string |
Fully-qualified repository name in owner/repo format. |
stacks |
[]Stack |
Array of stack objects representing GitHub Stacks (groups of PRs). |
Stack Object Structure
Each entry in the stacks array contains the following fields:
| Field | Go Type | Description |
|---|---|---|
id |
string |
Global GitHub-wide stack identifier (opaque string). |
number |
int |
Repository-scoped stack number shown in the UI; may be 0 in older files and is back-filled from the API. |
url |
string |
Direct link to the Stack on GitHub (omitempty). |
merged |
bool |
true when the entire stack has been merged (omitempty). |
trunk |
BranchRef |
The base branch of the stack (usually main or master). |
branches |
[]BranchRef |
Ordered list of branches from bottom (trunk) to top. |
Branch Reference Fields
The BranchRef objects stored in branches and trunk contain:
| Field | Go Type | Description |
|---|---|---|
branch |
string |
The branch name (e.g., feat/login). |
head |
string |
SHA of the branch tip (omitempty). |
base |
string |
SHA of the parent commit (omitempty). |
pullRequest |
*PullRequestRef |
PR metadata including id, number, and url (omitempty). |
merged |
bool |
true if the specific PR has been merged (omitempty). |
queued |
bool |
Transient flag set when the PR is in a merge queue; not persisted (marked with json:"-" in the Go struct). |
How the Stack File Is Managed in .git/gh-stack
The internal/stack/stack.go file implements atomic operations to prevent data corruption during concurrent access. According to the AGENTS.md documentation, these primitives ensure safe manipulation of the JSON metadata.
Loading and Initialization
When commands need stack data, the Load(dir) function reads .git/gh-stack. If the file is missing, it returns a new empty StackFile struct with SchemaVersion set to the current constant. The loader validates that schemaVersion matches the expected value, rejecting files written by newer tool versions with an explicit error message.
Atomic Saving and Schema Versioning
After mutating the in-memory struct (e.g., adding branches via cmd/add.go), the Save(sf) function writes changes back to disk. The implementation uses json.MarshalIndent for human-readable JSON and refreshes the SchemaVersion field to the current constant before writing.
File Locking and Concurrency Control
To prevent race conditions, gh-stack acquires an exclusive lock on .git/gh-stack.lock before any write operation. The lock implementation enforces a 5-second timeout; if another process holds the lock, the operation fails with a LockError. This ensures that concurrent gh stack commands do not corrupt the JSON structure.
Staleness Detection
The Load function calculates a checksum of the file contents when reading. On Save, the implementation compares the current on-disk checksum against the stored value. If they differ, indicating an external modification since the last load, the save aborts with a StaleError. This forces the caller to reload and re-apply changes, preventing silent overwrites.
Practical CLI Usage
While the Go code handles the JSON directly, users interact with the file through CLI commands that internally call these safe operations:
# Initialize a new stack file (creates .git/gh-stack if missing)
gh stack init
# Add a branch to the current stack (updates JSON via Load/Save)
gh stack add feat/login
# Inspect the raw JSON for debugging
cat .git/gh-stack | jq .
The cmd/init.go and cmd/add.go entry points demonstrate how high-level commands delegate to the Load and Save functions, ensuring the locking and validation logic always applies.
Summary
- The gh-stack stack file format is a JSON document stored at
.git/gh-stackthat tracks stacked PR metadata, branch SHAs, and merge status according tointernal/stack/schema.json. - The schema requires
schemaVersionto be1and defines structures forstacks,BranchRef, andPullRequestRefas implemented ininternal/stack/stack.go. - File operations are atomic:
Load(dir)validates and reads, whileSave(sf)writes withjson.MarshalIndentafter acquiring a lock on.git/gh-stack.lock. - Concurrency protection includes a 5-second timeout lock mechanism and checksum-based staleness detection to prevent data races.
- The
queuedfield onBranchRefis transient (markedjson:"-") and never persists to disk, only existing in memory during merge queue operations.
Frequently Asked Questions
Where is the gh-stack metadata file located?
The file is located at .git/gh-stack relative to your repository root. This path is hardcoded in internal/stack/stack.go and referenced in AGENTS.md as the canonical location for stacked branch metadata.
What happens if two gh-stack commands run simultaneously?
The second command will fail with a LockError. The implementation in internal/stack/stack.go creates a .git/gh-stack.lock file with a 5-second timeout. If the lock cannot be acquired within this window, the operation aborts to prevent JSON corruption.
Can I manually edit the .git/gh-stack file?
Manual editing is possible but risky. The Save function computes a checksum at load time; if the file changes on disk before your save operation completes, the system throws a StaleError. Any manual modifications must respect the JSON Schema in internal/stack/schema.json and avoid changing schemaVersion from 1.
How does gh-stack handle schema version mismatches?
If Load encounters a schemaVersion greater than the current constant, it aborts with an error message stating the file was written by a newer version of the tool. This prevents older binaries from corrupting data structures they do not recognize, forcing users to upgrade gh-stack to interact with newer stack files.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →