coreybutler/nvm-windows · error

panic(ferr)

Error message

panic(ferr)

What it means

This panic fires when writeToErrorLog cannot append a message to error.log next to the executable. The function opens the log with os.ModePerm and, if the subsequent WriteString call fails, it panics instead of returning the error. Because this is the error-logging path itself, the panic masks the original error being logged and crashes the whole process.

Source

Thrown at src/nvm.go:85

	node_mirror:     "",
	npm_mirror:      "",
	proxy:           "none",
	originalpath:    "",
	originalversion: "",
	verifyssl:       true,
}

func writeToErrorLog(i interface{}, abort ...bool) {
	exe, _ := os.Executable()
	file, err := os.OpenFile(filepath.Join(filepath.Dir(exe), "error.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	msg := fmt.Sprintf("%v\n", i)
	if _, ferr := file.WriteString(msg); ferr != nil {
		panic(ferr)
	}

	if len(abort) > 0 && abort[0] {
		fmt.Println(msg)
		os.Exit(1)
	}
}

type Notification struct {
	AppID    string   `json:"app_id"`
	Title    string   `json:"title"`
	Message  string   `json:"message"`
	Icon     string   `json:"icon"`
	Actions  []Action `json:"actions"`
	Duration string   `json:"duration"`
	Link     string   `json:"link"`
}

View on GitHub (pinned to 5b18223ca1)

Solutions

  1. Run the executable from a directory the process can write to (e.g. a user-owned install dir) so error.log is writable.
  2. Give the running user/group write permission on the executable's directory or pre-create error.log with appropriate ownership.
  3. Refactor writeToErrorLog to return an error (or log to stderr) instead of panicking, so a failed log write never crashes the app: fmt.Fprintln(os.Stderr, msg) as the fallback.
  4. Write the log to a writable location such as os.TempDir() or an env-configured path (e.g. NVM_HOME / XDG state dir) instead of filepath.Dir(exe).
  5. Free disk space / close other processes holding error.log if the failure is environmental.

Example fix

// before
if _, ferr := file.WriteString(msg); ferr != nil {
	panic(ferr)
}

// after
if _, ferr := file.WriteString(msg); ferr != nil {
	// never crash the process on the logging path itself
	fmt.Fprintf(os.Stderr, "writeToErrorLog: failed to write error.log: %v (original message: %v)\n", ferr, i)
}
Defensive patterns

Strategy: validation

Validate before calling

// Verify the error.log directory is writable before any logging can panic
func errorLogWritable() bool {
	exe, err := os.Executable()
	if err != nil {
		return false
	}
	p := filepath.Join(filepath.Dir(exe), "error.log")
	f, err := os.OpenFile(p, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
	if err != nil {
		return false
	}
	return f.Close() == nil
}

Prevention

When it happens

Trigger: Calling writeToErrorLog(msg) (or any wrapper such as a notification/log helper that routes through it) when: the directory containing the executable is read-only (e.g. /usr/bin, C:\Program Files); error.log is locked by another process on Windows; the disk is full; or the file is opened elsewhere with a conflicting lock. The open may succeed while WriteString fails on ENOSPC/EIO/permission races.

Common situations: Installing the Go binary into a protected system directory so the log file cannot be written; running as a service account without write permission to the exe folder; disk-full conditions on a long-running machine; antivirus/file-locking on Windows momentarily denying the write.

Related errors


AI-assisted analysis of coreybutler/nvm-windows@5b18223ca1 (2026-08-15). Data as JSON: /api/errors/683c3118edcd503d. Report an issue: GitHub.