Skip to content

Catalog migration: one conflict between unrelated old worktrees blocks every gh stack command #613

Open

Description

@schickling-assistant

Problem

When the per-worktree stack catalogs are migrated to the shared catalog, one conflict between two unrelated, old worktrees blocks every gh stack command in every worktree of the clone, including read-only ones like gh stack view --json:

migrating stack state: cannot migrate stack catalogs: stack identity has differing definitions; sources: [".../worktrees/<old-a>/gh-stack" ".../worktrees/<old-b>/gh-stack"] ...

In our case the two conflicting catalogs belonged to stale worktrees whose branches have nothing to do with the stack being viewed. Nobody working in the current worktree can proceed without editing other worktrees' catalogs.

Cause

Every command resolves stack state through MigrateLegacyState (

if err := stack.MigrateLegacyState(commonDir); err != nil {
,
if err := stack.MigrateLegacyState(commonDir); err != nil {
). mergeMigrationCatalogs returns on the first stack-identity or shared-branch conflict and the whole migration aborts (
func mergeMigrationCatalogs(commonDir string, catalogs []migrationCatalog) (*StackFile, error) {
merged := &StackFile{SchemaVersion: schemaVersion, Stacks: []Stack{}}
var sources []string
repositorySource := ""
for _, catalog := range catalogs {
path := filepath.Join(commonDir, filepath.FromSlash(catalog.Path))
sf, err := parseMigrationCatalog(catalog)
if err != nil {
var conflict *MigrationConflictError
if errors.As(err, &conflict) {
conflict.Sources = []string{path}
}
return nil, fmt.Errorf("parsing migration catalog %q: %w", path, err)
}
if sf.Repository != "" {
if merged.Repository != "" && merged.Repository != sf.Repository {
return nil, migrationConflict("repository identities differ", []string{repositorySource, path}, nil)
}
merged.Repository = sf.Repository
repositorySource = path
}
for _, s := range sf.Stacks {
duplicate := false
for i, other := range merged.Stacks {
if equivalentMigrationStacks(s, other) {
duplicate = true
break
}
var shared []string
for _, branch := range s.Branches {
if other.IndexOf(branch.Branch) >= 0 {
shared = append(shared, branch.Branch)
}
}
sameIdentity := (s.ID != "" && s.ID == other.ID) || (s.Number != 0 && s.Number == other.Number)
if sameIdentity {
branches := append(s.BranchNames(), other.BranchNames()...)
return nil, migrationConflict("stack identity has differing definitions", []string{sources[i], path}, branches)
}
if len(shared) > 0 {
return nil, migrationConflict("non-trunk branches belong to differing stack definitions", []string{sources[i], path}, shared)
}
}
if !duplicate {
merged.Stacks = append(merged.Stacks, s)
sources = append(sources, path)
}
}
}
return merged, nil
}
).

Related: #595 (same error after git fsck) and #610 (an unrelated duplicate worktree checkout blocks all commands), same pattern of one unrelated item failing the whole clone.

Expected

Migrate the stacks that merge cleanly and keep conflicting definitions out of the shared catalog (left in place, or quarantined), then fail only commands that touch the conflicting stacks or branches. A recovery command that lists and resolves the conflicts would replace hand-editing worktree internals.


This issue was drafted with AI assistance.

Posted on behalf of @schickling
field value
agent_identity agent/dotfiles/upstream-manager
session 01a10cc8
agent_persona generalist
agent_supervisor unavailable
agent_tool OMP
agent_tool_version 18.12.5
agent_runtime OMP 18.12.5
agent_model anthropic/claude-opus-5-5
worktree unknown

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions