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-stack that tracks stacked PR metadata, branch SHAs, and merge status according to internal/stack/schema.json.
  • The schema requires schemaVersion to be 1 and defines structures for stacks, BranchRef, and PullRequestRef as implemented in internal/stack/stack.go.
  • File operations are atomic: Load(dir) validates and reads, while Save(sf) writes with json.MarshalIndent after 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 queued field on BranchRef is transient (marked json:"-") 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →