{"record":{"id":"e14c17e5991ceb3a","repo":"asdf-vm/asdf","slug":"unable-to-create-file-plugin-index-touch-file-w","errorCode":null,"errorMessage":"unable to create file plugin index touch file: %w","messagePattern":"unable to create file plugin index touch file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginindex/pluginindex.go","lineNumber":136,"sourceCode":"func (p PluginIndex) GetPluginSourceURL(name string) (string, error) {\n\t_, err := p.Refresh()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\turl, err := readPlugin(p.directory, name)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn url, nil\n}\n\nfunc touchFS(directory string) (bool, error) {\n\tfilename := filepath.Join(directory, repoUpdatedFilename)\n\tfile, err := os.OpenFile(filename, os.O_RDONLY|os.O_CREATE, 0o666)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"unable to create file plugin index touch file: %w\", err)\n\t}\n\n\tfile.Close()\n\treturn true, nil\n}\n\nfunc lastUpdated(dir string) (int64, error) {\n\tinfo, err := os.Stat(filepath.Join(dir, repoUpdatedFilename))\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"unable to read last updated file: %w\", err)\n\t}\n\n\t// info.Atime_ns now contains the last access time\n\tupdated := time.Now().UnixNano() - info.ModTime().UnixNano()\n\treturn updated, nil\n}\n\nfunc readPlugin(dir, name string) (string, error) {","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/asdf-vm/asdf/blob/074a1722ca88d6677f228541ce06efaf4f9924d4/internal/pluginindex/pluginindex.go#L118-L154","documentation":"touchFS records that the plugin index was created/updated by creating (or opening) a zero-byte touch file (repoUpdatedFilename) inside the index directory via os.OpenFile. If the file cannot be created/opened, the error is wrapped as 'unable to create file plugin index touch file'. It is a local filesystem failure, not a git failure.","triggerScenarios":"Refresh or doUpdate reaching the touch step when: the index directory was deleted between clone/update and touch; permissions on the directory deny writing; the touch file exists but is unwritable/unopenable; disk is full.","commonSituations":"Concurrent asdf processes racing where one removes the directory while another touches; ASDF data dir on read-only media; sandboxed/CI environments with restricted write paths; SELinux/AppArmor denials.","solutions":["Ensure the index directory exists and is writable by the current user (mkdir -p, chown/chmod).","Check for a file (not directory) at the index directory path and remove it.","Avoid running multiple asdf index refreshes concurrently.","Verify the data dir is not on read-only or quota-exhausted storage."],"exampleFix":"// before\nexport ASDF_DATA_DIR=/mnt/ro-media/asdf\n// after\nexport ASDF_DATA_DIR=\"$HOME/.asdf\"\nmkdir -p \"$ASDF_DATA_DIR/repository\"","handlingStrategy":"validation","validationCode":"if fi, err := os.Stat(directory); err != nil || !fi.IsDir() {\n    os.MkdirAll(directory, 0o755)\n}\nprobe := filepath.Join(directory, \".write_probe\")\nif err := os.WriteFile(probe, nil, 0o666); err != nil {\n    return fmt.Errorf(\"index dir not writable: %w\", err)\n}\nos.Remove(probe)","typeGuard":null,"tryCatchPattern":"ok, err := idx.Refresh()\nif err != nil && strings.Contains(err.Error(), \"touch file\") {\n    log.Printf(\"cannot write in %s; check perms/mount: %v\", indexDir, err)\n}","preventionTips":["Ensure the asdf data dir is writable by the invoking user.","Do not run concurrent asdf processes that recreate the index dir.","Check SELinux/AppArmor policies in hardened environments.","Avoid read-only or quota-exhausted mounts for ASDF_DATA_DIR."],"tags":["filesystem","permissions","plugin-index"],"backgroundTag":"file-open-permission-denied","analyzedSha":"074a1722ca88d6677f228541ce06efaf4f9924d4","analyzedAt":"2026-08-30T19:56:42.972Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}