charmbracelet/crush · error

failed to tail log file: %v

Error message

failed to tail log file: %v

What it means

followLogs opens the crush.log file with the hpcloud/tail library's tail.TailFile (Follow:false, ReOpen:false). If the file cannot be opened or a tailer cannot be created (missing file, permission denied, path is a directory), the error is wrapped with this message. Note the follow path uses Follow:false here, so it reads currently available lines only.

Source

Thrown at internal/cmd/logs.go:85

		}

		return showLogs(logsFile, tailLines)
	},
}

func init() {
	logsCmd.Flags().BoolP("follow", "f", false, "Follow log output")
	logsCmd.Flags().IntP("tail", "t", defaultTailLines, "Show only the last N lines default: 1000 for performance")
}

func followLogs(ctx context.Context, logsFile string, tailLines int) error {
	t, err := tail.TailFile(logsFile, tail.Config{
		Follow: false,
		ReOpen: false,
		Logger: tail.DiscardingLogger,
	})
	if err != nil {
		return fmt.Errorf("failed to tail log file: %v", err)
	}

	var lines []string
	for line := range t.Lines {
		if line.Err != nil {
			continue
		}
		lines = append(lines, line.Text)
		if len(lines) > tailLines {
			lines = lines[len(lines)-tailLines:]
		}
	}
	t.Stop()

	for _, line := range lines {
		printLogLine(line)
	}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Verify the log file exists and is readable: ls -l <data-dir>/logs/crush.log; fix permissions with chmod if needed.
  2. Ensure you are passing the correct --data-dir / --cwd so the resolved path points to a real crush project.
  3. Re-run the crush project once to (re)generate the log file, then view logs.
  4. If developing, re-check the file after os.Stat and fall back to a friendly 'no logs' message instead of the raw tail error.

Example fix

// before
if _, err = os.Stat(logsFile); os.IsNotExist(err) {
    log.Warn("no logs")
    return nil
}
// after: also handle open failure gracefully
if _, err = os.Stat(logsFile); os.IsNotExist(err) {
    log.Warn("Looks like you are not in a crush project. No logs found.")
    return nil
}
if err := os.Chmod(logsFile, 0o644); err != nil { /* inspect */ }
Defensive patterns

Strategy: validation

Validate before calling

// Go: ensure the log file exists and is readable before tailing
if fi, err := os.Stat(logsFile); err != nil || fi.IsDir() {
    return fmt.Errorf("log file unavailable: %s", logsFile)
}
if f, err := os.Open(logsFile); err != nil {
    return fmt.Errorf("log file unreadable: %w", err)
} else {
    f.Close()
}

Try / catch

t, err := tail.TailFile(logsFile, tail.Config{Follow: false, ReOpen: false, Logger: tail.DiscardingLogger})
if err != nil {
    if os.IsNotExist(errors.Unwrap(err)) || errors.Is(err, fs.ErrNotExist) {
        log.Warn("No logs found.")
        return nil
    }
    return fmt.Errorf("failed to tail log file: %w", err)
}

Prevention

When it happens

Trigger: Calling followLogs with a logsFile path that does not exist (log file deleted between the os.Stat check and the tail), is unreadable due to permissions, or is a directory; also exotic cases like the file being on a filesystem tail cannot poll.

Common situations: Running `crush logs` right after cleaning the data directory; log file permissions changed by another process; custom --data-dir pointing somewhere without a logs/crush.log; race between project shutdown and log viewing.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/883f969a0771b174. Report an issue: GitHub.