{"record":{"id":"e611eedd9e8fb7ec","repo":"asdf-vm/asdf","slug":"unable-to-read-last-updated-file-w","errorCode":null,"errorMessage":"unable to read last updated file: %w","messagePattern":"unable to read last updated file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginindex/pluginindex.go","lineNumber":146,"sourceCode":"\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) {\n\tfilename := filepath.Join(dir, \"plugins\", name)\n\n\tpluginInfo, err := ini.Load(filename)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"plugin %s not found in repository\", name)\n\t}\n\n\treturn pluginInfo.Section(\"\").Key(\"repository\").String(), nil\n}\n","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/asdf-vm/asdf/blob/074a1722ca88d6677f228541ce06efaf4f9924d4/internal/pluginindex/pluginindex.go#L128-L164","documentation":"lastUpdated reads the modification time of the index touch file to compute how stale the index is. If os.Stat on the touch file (repoUpdatedFilename) fails, the error is wrapped as 'unable to read last updated file'. Typically the touch file was never created because the index was never initialized.","triggerScenarios":"Refresh calling lastUpdated when: the touch file does not exist yet (first run, partially completed init); the index directory was deleted or recreated without the touch file; permission denied on Stat; the directory path is wrong.","commonSituations":"A previous clone succeeded but touchFS failed or was interrupted; user manually cleaned the asdf repository directory; race between two asdf processes; data dir moved between asdf versions.","solutions":["Let Refresh recover: a missing touch file leads to re-initialization — clear any stale/corrupt index directory and rerun.","Recreate the index from scratch: rm -rf the index/repository dir under ASDF_DATA_DIR, then run any plugin index command.","Check permissions on the index directory so the touch file can be read.","Ensure two asdf processes are not racing on the same data dir."],"exampleFix":"// before: half-initialized index missing touch file\nls ~/.asdf/repository/plugin-index  # file absent\n// after: reset the index\nrm -rf \"${ASDF_DATA_DIR:-$HOME/.asdf}/repository\"\nasdf plugin list all","handlingStrategy":"validation","validationCode":"touch := filepath.Join(indexDir, \"plugin-index-updated\")\nif _, err := os.Stat(touch); os.IsNotExist(err) {\n    // index never initialized; force a fresh Refresh/clone\n    os.RemoveAll(indexDir)\n}","typeGuard":null,"tryCatchPattern":"ok, err := idx.Refresh()\nif err != nil && strings.Contains(err.Error(), \"unable to read last updated file\") {\n    os.RemoveAll(indexDir)\n    ok, err = idx.Refresh() // re-initialize from scratch\n}","preventionTips":["Let asdf manage its repository dir; do not hand-delete parts of it.","After manually cleaning the data dir, run an index command to reinitialize.","Avoid racing asdf invocations in parallel scripts.","Keep the whole data dir together when moving ASDF_DATA_DIR."],"tags":["filesystem","plugin-index","staleness"],"backgroundTag":"file-not-found","analyzedSha":"074a1722ca88d6677f228541ce06efaf4f9924d4","analyzedAt":"2026-08-30T19:56:42.972Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}