{"record":{"id":"0122b964f5df9187","repo":"gastownhall/beads","slug":"invalid-childref-s-must-be-alphanumeric-dash","errorCode":null,"errorMessage":"invalid childRef '%s': must be alphanumeric, dash, underscore, or dot only","messagePattern":"invalid childRef '(.+?)': must be alphanumeric, dash, underscore, or dot only","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/template.go","lineNumber":516,"sourceCode":"//   - Root: parent.childref (e.g., \"patrol-x7k.arm-ace\")\n//   - Children: parent.childref.step (e.g., \"patrol-x7k.arm-ace.capture\")\n//\n// The childRef is variable-substituted before use.\n// Returns empty string if not a bonded operation (opts.ParentID empty).\nfunc generateBondedID(oldID string, rootID string, opts CloneOptions) (string, error) {\n\tif opts.ParentID == \"\" {\n\t\treturn \"\", nil // Not a bonded operation\n\t}\n\n\t// Substitute variables in childRef\n\tchildRef := substituteVariables(opts.ChildRef, opts.Vars)\n\n\t// Validate childRef after substitution\n\tif childRef == \"\" {\n\t\treturn \"\", fmt.Errorf(\"childRef is empty after variable substitution\")\n\t}\n\tif !bondedIDPattern.MatchString(childRef) {\n\t\treturn \"\", fmt.Errorf(\"invalid childRef '%s': must be alphanumeric, dash, underscore, or dot only\", childRef)\n\t}\n\n\tif oldID == rootID {\n\t\t// Root issue: parent.childref\n\t\tnewID := fmt.Sprintf(\"%s.%s\", opts.ParentID, childRef)\n\t\treturn newID, nil\n\t}\n\n\t// Child issue: parent.childref.relative\n\t// Extract the relative portion of the old ID (part after root)\n\trelativeID := getRelativeID(oldID, rootID)\n\tif relativeID == \"\" {\n\t\t// No hierarchical relationship - use a suffix from the old ID to ensure uniqueness.\n\t\t// Extract the last part of the old ID (after any prefix or dash)\n\t\tsuffix := extractIDSuffix(oldID)\n\t\tnewID := fmt.Sprintf(\"%s.%s.%s\", opts.ParentID, childRef, suffix)\n\t\treturn newID, nil\n\t}","sourceCodeStart":498,"sourceCodeEnd":534,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/template.go#L498-L534","documentation":"After substituting variables, generateBondedID validates childRef against bondedIDPattern (alphanumeric, dash, underscore, dot only). If the resolved childRef contains other characters — spaces, slashes, '@', etc. — it throws 'invalid childRef %s: must be alphanumeric, dash, underscore, or dot only', because the result would form an invalid issue ID when combined with the parent ID.","triggerScenarios":"Calling cloneSubgraphInto / generateBondedID with a ChildRef (literal or post-substitution) containing forbidden characters: 'my task' (space), 'a/b' (slash), '{{name}}' resolving to a value with spaces or punctuation.","commonSituations":"Deriving childRef from free-form user input or issue titles that contain spaces/slashes; variable values pulled from filenames or URLs; shell-quoted values retaining special characters.","solutions":["Sanitize the childRef value before passing it: replace disallowed characters with '-' or '_'.","Use a slugified version of titles/filenames instead of raw strings.","Choose a literal childRef that already matches [A-Za-z0-9._-].","Validate user input at the CLI boundary before it reaches CloneOptions."],"exampleFix":"// before\nopts := CloneOptions{ChildRef: \"fix login page\"} // space invalid\n// after\nopts := CloneOptions{ChildRef: \"fix-login-page\"}","handlingStrategy":"validation","validationCode":"var bondedIDPattern = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)\nref := substituteVariables(opts.ChildRef, opts.Vars)\nif !bondedIDPattern.MatchString(ref) {\n\treturn fmt.Errorf(\"childRef %q contains invalid characters\", ref)\n}","typeGuard":null,"tryCatchPattern":"newID, err := generateBondedID(ctx, tx, oldID, rootID, opts)\nif err != nil && strings.Contains(err.Error(), \"invalid childRef\") {\n\treturn fmt.Errorf(\"sanitize childRef to [A-Za-z0-9._-]: %w\", err)\n}","preventionTips":["Slugify any user-provided or derived childRef (replace spaces/slashes with '-').","Validate childRef against ^[A-Za-z0-9._-]+$ at the CLI boundary.","Never pass raw filenames, titles, or URLs as childRef."],"tags":["template","validation","naming","bonding"],"backgroundTag":"invalid-identifier-format","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}