{"record":{"id":"f413a709f1fdb499","repo":"twpayne/chezmoi","slug":"s-empty-path","errorCode":null,"errorMessage":"%s: empty path","messagePattern":"(.+?): empty path","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/chezmoi/sourcestate.go","lineNumber":1454,"sourceCode":"\tif err != nil {\n\t\treturn err\n\t}\n\tdata, err := s.executeTemplate(sourceAbsPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s: %w\", sourceAbsPath, err)\n\t}\n\texternals := make(map[string]External)\n\tif err := format.Unmarshal(data, &externals); err != nil {\n\t\treturn fmt.Errorf(\"%s: %w\", sourceAbsPath, err)\n\t}\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\tfor targetPath, external := range externals {\n\t\tif external.TargetPath != \"\" {\n\t\t\ttargetPath = external.TargetPath\n\t\t}\n\t\tif targetPath == \"\" {\n\t\t\treturn fmt.Errorf(\"%s: empty path\", sourceAbsPath)\n\t\t}\n\n\t\texternalPath := path.Clean(targetPath)\n\t\tif strings.HasPrefix(externalPath, \"/\") || filepath.IsAbs(externalPath) {\n\t\t\treturn fmt.Errorf(\"%s: %s: path is not relative\", sourceAbsPath, targetPath)\n\t\t}\n\t\tswitch relPath, err := filepath.Rel(\".\", externalPath); {\n\t\tcase err != nil:\n\t\t\treturn fmt.Errorf(\"%s: %s: %w\", sourceAbsPath, targetPath, err)\n\t\tcase relPath == \".\":\n\t\t\treturn fmt.Errorf(\"%s: %s: empty relative path\", sourceAbsPath, targetPath)\n\t\tcase relPath == \"..\", strings.HasPrefix(relPath, \"../\"),\n\t\t\truntime.GOOS == \"windows\" && strings.HasPrefix(relPath, \"..\"+string(filepath.Separator)):\n\t\t\treturn fmt.Errorf(\"%s: %s: relative path outside target directory\", sourceAbsPath, targetPath)\n\t\t}\n\t\ttargetRelPath := parentTargetSourceRelPath.JoinString(externalPath)\n\t\texternal.sourceAbsPath = sourceAbsPath\n\t\ts.externals[targetRelPath] = append(s.externals[targetRelPath], &external)","sourceCodeStart":1436,"sourceCodeEnd":1472,"githubUrl":"https://github.com/twpayne/chezmoi/blob/f901167e4685db90da56d6a2a19df642cb3e0247/internal/chezmoi/sourcestate.go#L1436-L1472","documentation":"Each external entry must declare a target path (the key, or the TargetPath field override). If the resolved targetPath is the empty string, chezmoi cannot determine where to place the external and returns this error naming the source external file.","triggerScenarios":"An external map entry with an empty-string key combined with no TargetPath field, or a TargetPath explicitly set to \"\" which fails the != \"\" check so the (empty) map key is used.","commonSituations":"Template rendering that evaluates a key/path to empty (e.g. {{ .missing }} yielding \"\"); copy-pasted external entries where the key was deleted; programmatic generation producing blank keys.","solutions":["Give each external entry a non-empty key path (the relative target path) in .chezmoiexternal.","If using TargetPath, set it to a concrete relative path instead of an empty string.","Fix the template that renders the key so it yields a real value."],"exampleFix":"// before\n[\"\"]\ntype = \"archive\"\nurl = \"https://example.com/x.tar.gz\"\n// after\n[\".local/bin/tool\"]\ntype = \"archive\"\nurl = \"https://example.com/x.tar.gz\"","handlingStrategy":"validation","validationCode":"// ensure every external entry has a non-empty target\nfor key, ext := range externals {\n  target := ext.TargetPath\n  if target == \"\" { target = key }\n  if target == \"\" { return fmt.Errorf(\"external entry %q has empty path\", key) }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always give external entries explicit non-empty keys","Avoid templates in keys that can evaluate to empty","Review generated external files for blank keys"],"tags":["chezmoi","external","path-validation"],"backgroundTag":"empty-path-config","analyzedSha":"f901167e4685db90da56d6a2a19df642cb3e0247","analyzedAt":"2026-09-01T18:16:41.508Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T01:17:15.007Z"}