asdf-vm/asdf · error
unable to create file plugin index touch file: %w
Error message
unable to create file plugin index touch file: %w
What it means
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.
Source
Thrown at internal/pluginindex/pluginindex.go:136
func (p PluginIndex) GetPluginSourceURL(name string) (string, error) {
_, err := p.Refresh()
if err != nil {
return "", err
}
url, err := readPlugin(p.directory, name)
if err != nil {
return "", err
}
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) {View on GitHub (pinned to 074a1722ca)
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.
Example fix
// before export ASDF_DATA_DIR=/mnt/ro-media/asdf // after export ASDF_DATA_DIR="$HOME/.asdf" mkdir -p "$ASDF_DATA_DIR/repository"
Defensive patterns
Strategy: validation
Validate before calling
if fi, err := os.Stat(directory); err != nil || !fi.IsDir() {
os.MkdirAll(directory, 0o755)
}
probe := filepath.Join(directory, ".write_probe")
if err := os.WriteFile(probe, nil, 0o666); err != nil {
return fmt.Errorf("index dir not writable: %w", err)
}
os.Remove(probe) Try / catch
ok, err := idx.Refresh()
if err != nil && strings.Contains(err.Error(), "touch file") {
log.Printf("cannot write in %s; check perms/mount: %v", indexDir, err)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- unable to create download dir: %w
- unable to create install dir: %w
- unable to read last updated file: %w
- unable to check if plugin already exists: %w
- unable to check if plugin exists: %w
AI-assisted analysis of asdf-vm/asdf@074a1722ca (2026-08-30).
Data as JSON: /api/errors/e14c17e5991ceb3a.
Report an issue: GitHub.