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

  1. Ensure the index directory exists and is writable by the current user (mkdir -p, chown/chmod).
  2. Check for a file (not directory) at the index directory path and remove it.
  3. Avoid running multiple asdf index refreshes concurrently.
  4. 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

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


AI-assisted analysis of asdf-vm/asdf@074a1722ca (2026-08-30). Data as JSON: /api/errors/e14c17e5991ceb3a. Report an issue: GitHub.