asdf-vm/asdf · error
unable to read last updated file: %w
Error message
unable to read last updated file: %w
What it means
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.
Source
Thrown at internal/pluginindex/pluginindex.go:146
return url, nil
}
func touchFS(directory string) (bool, error) {
filename := filepath.Join(directory, repoUpdatedFilename)
file, err := os.OpenFile(filename, os.O_RDONLY|os.O_CREATE, 0o666)
if err != nil {
return false, fmt.Errorf("unable to create file plugin index touch file: %w", err)
}
file.Close()
return true, nil
}
func lastUpdated(dir string) (int64, error) {
info, err := os.Stat(filepath.Join(dir, repoUpdatedFilename))
if err != nil {
return 0, fmt.Errorf("unable to read last updated file: %w", err)
}
// info.Atime_ns now contains the last access time
updated := time.Now().UnixNano() - info.ModTime().UnixNano()
return updated, nil
}
func readPlugin(dir, name string) (string, error) {
filename := filepath.Join(dir, "plugins", name)
pluginInfo, err := ini.Load(filename)
if err != nil {
return "", fmt.Errorf("plugin %s not found in repository", name)
}
return pluginInfo.Section("").Key("repository").String(), nil
}
View on GitHub (pinned to 074a1722ca)
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.
Example fix
// before: half-initialized index missing touch file
ls ~/.asdf/repository/plugin-index # file absent
// after: reset the index
rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}/repository"
asdf plugin list all Defensive patterns
Strategy: validation
Validate before calling
touch := filepath.Join(indexDir, "plugin-index-updated")
if _, err := os.Stat(touch); os.IsNotExist(err) {
// index never initialized; force a fresh Refresh/clone
os.RemoveAll(indexDir)
} Try / catch
ok, err := idx.Refresh()
if err != nil && strings.Contains(err.Error(), "unable to read last updated file") {
os.RemoveAll(indexDir)
ok, err = idx.Refresh() // re-initialize from scratch
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- unable to create file plugin index touch file: %w
- unable to fetch current directory: %w
- unable to create download dir: %w
- unable to create install dir: %w
- unable to initialize index: %w
AI-assisted analysis of asdf-vm/asdf@074a1722ca (2026-08-30).
Data as JSON: /api/errors/e611eedd9e8fb7ec.
Report an issue: GitHub.