{"record":{"id":"d316ce1bd5faa612","repo":"gastownhall/beads","slug":"symbolic-ref-chain-for-q-exceeds-16-links","errorCode":null,"errorMessage":"symbolic ref chain for %q exceeds 16 links","messagePattern":"symbolic ref chain for %q exceeds 16 links","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/worktree_cmd.go","lineNumber":1928,"sourceCode":"\t\t}\n\t\tseen[current] = struct{}{}\n\n\t\toutput, err := git.output(ctx, executionRoot, \"symbolic-ref\", \"--quiet\", current)\n\t\tif err == nil {\n\t\t\tnext := strings.TrimSpace(string(output))\n\t\t\tif !strings.HasPrefix(next, \"refs/\") {\n\t\t\t\treturn \"\", fmt.Errorf(\"symbolic ref %q resolves outside refs/: %q\", current, next)\n\t\t\t}\n\t\t\tcurrent = next\n\t\t\tcontinue\n\t\t}\n\t\tvar exitError *exec.ExitError\n\t\tif errors.As(err, &exitError) && exitError.ExitCode() == 1 {\n\t\t\treturn current, nil\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"failed to inspect symbolic ref %q: %w\", current, err)\n\t}\n\treturn \"\", fmt.Errorf(\"symbolic ref chain for %q exceeds 16 links\", ref)\n}\n\nfunc resolveWorktreeCommitOID(\n\tctx context.Context,\n\tgit *worktreeRemovalGit,\n\texecutionRoot string,\n\trefOrOID string,\n) (string, error) {\n\toutput, err := git.output(\n\t\tctx,\n\t\texecutionRoot,\n\t\t\"rev-parse\",\n\t\t\"--verify\",\n\t\t\"--quiet\",\n\t\t\"--end-of-options\",\n\t\trefOrOID+\"^{commit}\",\n\t)\n\tif err != nil {","sourceCodeStart":1910,"sourceCodeEnd":1946,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/worktree_cmd.go#L1910-L1946","documentation":"The symbolic ref walker caps chain length at 16 links; a valid ref chain in any real repository terminates in far fewer hops. If the loop guard (cycle detection) has not fired after 16 hops, the chain is treated as pathological. This is a safety valve against unbounded git invocations.","triggerScenarios":"A symbolic ref chain of 17+ distinct links — possible only with adversarially or programmatically created refs, e.g. refs/a -> refs/b -> ... -> refs/q — encountered while resolving the comparison ref for worktree removal.","commonSituations":"Automated tooling that chains symbolic refs (each ref pointing to the next); migration scripts that rebuilt refs incorrectly; intentionally crafted repositories.","solutions":["Find the chain endpoint with repeated `git symbolic-ref` calls and flatten it: repoint the first ref directly at the final branch","Replace the whole chain with a single hard ref using `git update-ref`","Delete the redundant intermediate refs with `git update-ref -d`"],"exampleFix":"// before: long chain refs/a -> refs/b -> ... -> refs/heads/main\n$ git symbolic-ref refs/a refs/heads/main\n// after: single hop\n$ git symbolic-ref refs/a refs/heads/main","handlingStrategy":"validation","validationCode":"// bound-check the symbolic chain length before invoking bd\nfunc chainDepth(ref string, sym func(string) (string, bool)) int {\n\tfor depth := 0; depth < 16; depth++ {\n\t\tnext, ok := sym(ref)\n\t\tif !ok { return depth }\n\t\tref = next\n\t}\n\treturn -1 // exceeds safe depth\n}","typeGuard":null,"tryCatchPattern":"_, err := bd.WorktreeRemove(name)\nif err != nil && strings.Contains(err.Error(), \"exceeds 16 links\") {\n\t// flatten the ref chain manually, then retry\n\treturn flattenRefsAndRetry(name)\n}","preventionTips":["Do not build chains of symbolic refs in automation; one level (branch -> HEAD target) is enough","Flatten indirection with `git update-ref <ref> <oid>`","Audit with `git for-each-ref --format='%(refname) %(symref)'` for long symref chains"],"tags":["git","symbolic-ref","ref-chain","worktree"],"backgroundTag":"git-symbolic-ref-cycle","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}