How Beads’ Hash-Based ID System Prevents Merge Conflicts
Beads eliminates merge conflicts by replacing sequential counters with content-derived SHA-256 hashes that guarantee global uniqueness without coordination, making conflicting issue IDs impossible when branches merge.
The gastownhall/beads project solves a fundamental distributed systems challenge: generating identifiers across disconnected workspaces without central coordination. Traditional issue trackers rely on fragile sequential counters (#1, #2) that inevitably collide when two branches create issues independently. Beads prevents these conflicts by constructing identifiers from immutable content hashes, ensuring every issue carries a globally unique primary key by construction.
Deterministic Content-Based Hashing
At the core of the system is GenerateHashID in [internal/types/id_generator.go](https://github.com/gastownhall/beads/blob/main/internal/types/id_generator.go#L11-L22). When a user creates an issue, Beads computes a SHA-256 hash over four inputs: the title, description, creation timestamp (nanosecond precision), and workspace identifier.
// GenerateHashID creates a deterministic content-based hash ID.
h := sha256.New()
h.Write([]byte(title))
h.Write([]byte(description))
h.Write([]byte(created.Format(time.RFC3339Nano)))
h.Write([]byte(workspaceID))
hash := hex.EncodeToString(h.Sum(nil)) // full 64-char hex
The full 64-character hexadecimal string provides cryptographic uniqueness guarantees. For display purposes, Beads initially surfaces only the first 6–8 characters (e.g., bd-a3f2dd), keeping references concise while preserving the full hash as the authoritative primary key in storage.
Adaptive Hash Length Prevents Collisions
While 6-character base-36 suffixes offer only 24 bits of entropy (approximately 46,000 possibilities), Beads monitors collision rates and dynamically extends the visible ID length. The GenerateIssueIDInTable function in [internal/storage/issueops/helpers.go](https://github.com/gastownhall/beads/blob/main/internal/storage/issueops/helpers.go#L118-L150) implements this progressive strategy.
- 6 characters: Collision probability remains below 3% for workspaces with fewer than 1,000 issues.
- 7–8 characters: Automatically applied as volume increases, maintaining uniqueness without wasting space in smaller workspaces.
This adaptive approach keeps the common case short and readable while mathematically guaranteeing that no two issues share the same identifier.
Distributed Generation Without Coordination
Because the hash incorporates the workspace ID, two agents working in different workspaces can generate the same short suffix (e.g., bd-a1b2) without ever colliding—their full 64-byte hashes remain distinct. Even when two agents on the same workspace create issues simultaneously, the nanosecond-precision timestamp ensures different hashes. This eliminates the need for:
- Centralized ID counters.
- Network coordination during creation.
- Pre-merge ID renumbering.
Conflict-Free Storage with Dolt
Beads stores issues in Dolt, a version-controlled SQL database that merges rows at the cell level. When two branches contain different issues with distinct primary keys (the hash-based IDs), Dolt merges them trivially without user intervention. A merge conflict only arises if two rows share the same primary key, which the hash-based system prevents by construction. As implemented in gastownhall/beads, the storage layer never encounters the "same ID, different issue" scenario that plagues sequential counters.
Hierarchical Child IDs Preserve Namespace Uniqueness
For epics and subtasks, Beads extends the hash system with hierarchical identifiers. The GenerateChildID function (lines 43–50 of [internal/types/id_generator.go](https://github.com/gastownhall/beads/blob/main/internal/types/id_generator.go#L43-L50)) appends numeric suffixes to the parent hash:
Parent: bd-a3f8e9
Children: bd-a3f8e9.1, bd-a3f8e9.2, ...
Because the parent hash anchors the namespace, child IDs never clash with other epics, even when multiple users create subtasks concurrently.
Partial-ID Resolution Safely Maps Shortcuts
Users can reference issues by typing only the short prefix (e.g., bd show a1b2). The ResolvePartialID function in [internal/utils/id_parser.go](https://github.com/gastownhall/beads/blob/main/internal/utils/id_parser.go#L30-L100) performs a SQL-level LIKE query against the hash column, returning the unique full ID. Because the 64-byte hash space is effectively collision-free, this shortcut practically never resolves ambiguously.
Practical Examples
Create an issue without worrying about the next available number—the hash generates automatically:
# Create a new issue – ID is generated automatically
bd create "Add OAuth support" -p 2
# → bd-a1b2c3 (hash-based, no coordination needed)
# Create a child task under the newly created epic
bd create "Design login UI" --parent bd-a1b2c3
# → bd-a1b2c3.1 (hierarchical ID)
# Show an issue by typing only the short hash prefix
bd show a1b2 # resolves to bd-a1b2c3
Programmatic usage in Go:
import "github.com/gastownhall/beads/internal/types"
// deterministic hash for an issue
id := types.GenerateHashID(
"bd",
"Add OAuth support",
"Implement OAuth2 flow",
time.Now(),
"workspace-42",
)[:6] // → "a1b2c3"
fmt.Println("New ID:", "bd-"+id)
Summary
- Immutable content hashing: IDs derive from SHA-256 hashes of title, description, timestamp, and workspace, guaranteeing uniqueness by construction.
- Progressive length: 6-characterPrefixes expand to 7–8 characters automatically as collision probability increases.
- Zero coordination: Agents generate IDs independently without network consensus or central counters.
- Dolt-native storage: Cell-level merging combines branches conflict-free because every row has a unique primary key.
- Hierarchical safety: Child IDs inherit parent hash namespaces, preventing collisions in subtasks.
Frequently Asked Questions
What happens if two users create issues with identical titles and descriptions?
Even with identical content, the nanosecond-precision timestamp and workspace identifier ensure distinct hashes. The probability of collision is mathematically equivalent to finding a SHA-256 collision, which is computationally infeasible.
How does Beads handle the rare case of hash collisions?
The GenerateIssueIDInTable function detects collisions at the database layer and automatically extends the visible ID length from 6 to 7 or 8 characters. In practice, the 64-byte full hash makes collisions virtually impossible, but the system handles them gracefully by increasing entropy.
Why not use simple sequential IDs like traditional issue trackers?
Sequential counters require central coordination to avoid gaps and duplicates. When two branches create issue #5 independently, merging produces a conflict requiring manual resolution. Hash-based IDs eliminate this entirely because content, not sequence, determines identity.
How does partial ID resolution work without ambiguity?
ResolvePartialID queries the database using SQL LIKE patterns on the hash column. Because the full 64-byte hash has astronomically low collision probability, even a 4-character prefix almost always resolves to a single unique record. The system validates uniqueness before returning the result.
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 →