router-for-me/CLIProxyAPI · warning

invalid fingerprint boundary

Error message

invalid fingerprint boundary

What it means

Returned by logFileFingerprint (logs.go:1035) when the boundary (the byte position up to which the file is fingerprinted) exceeds the file's current size. newLogCursor computes the boundary from the offset/size it just Stat'ed; if the file shrinks between that Stat and the Open/Stat inside logFileFingerprint, the boundary is now past EOF and the check trips. It is a time-of-check/time-of-use race during rotation or truncation.

Source

Thrown at internal/api/handlers/management/logs.go:1035

	if boundary < 0 {
		return "", fmt.Errorf("invalid fingerprint boundary")
	}
	file, errOpen := os.Open(path)
	if errOpen != nil {
		return "", errOpen
	}
	defer func() {
		_ = file.Close()
	}()
	info, errStat := file.Stat()
	if errStat != nil {
		return "", errStat
	}
	if info.IsDir() {
		return "", fmt.Errorf("invalid log file")
	}
	if boundary > info.Size() {
		return "", fmt.Errorf("invalid fingerprint boundary")
	}

	hash := sha256.New()
	_, _ = fmt.Fprintf(hash, "log-cursor-v1:%d:", boundary)
	firstLen := minInt64(boundary, logCursorFingerprintMax)
	if errRead := writeFileRange(hash, file, 0, firstLen); errRead != nil {
		return "", errRead
	}
	tailLen := minInt64(boundary, logCursorFingerprintMax)
	tailStart := boundary - tailLen
	_, _ = fmt.Fprintf(hash, ":%d:", tailStart)
	if errRead := writeFileRange(hash, file, tailStart, tailLen); errRead != nil {
		return "", errRead
	}
	sum := hash.Sum(nil)
	return base64.RawURLEncoding.EncodeToString(sum[:12]), nil
}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Retry the same log request once — the race is transient and the retry will compute a fresh boundary against the new file size
  2. Switch rotation from copytruncate to rename+create so the active file is replaced atomically instead of being truncated in place
  3. If it persists, check that no process is continuously truncating the log file
  4. Start a fresh read without a cursor to resynchronize state

Example fix

# before: copytruncate shrinks the file mid-fingerprint
/var/log/cliproxy/cliproxy.log {
  copytruncate
  weekly
}

# after: rename-based rotation avoids in-place truncation
/var/log/cliproxy/cliproxy.log {
  create 0644 root root
  postrotate
    systemctl reload cliproxy || true
  endscript
}
Defensive patterns

Strategy: retry

Try / catch

for attempt := 0; attempt < 2; attempt++ {
	cursor, err := newLogCursor(path, offset, latest)
	if err == nil {
		break
	}
	if strings.Contains(err.Error(), "invalid fingerprint boundary") && attempt == 0 {
		continue // rotation raced us; recompute offset against new size
	}
	return err
}

Prevention

When it happens

Trigger: Log rotation or copytruncate fires exactly between newLogCursor's os.Stat and logFileFingerprint's open+stat, shrinking the file; rapid manual truncation while a tail client paginates.

Common situations: logrotate with copytruncate running on a busy server; a disk-cleanup job that truncates logs on a schedule that collides with dashboard polling.

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/4598e292dce2bbd4. Report an issue: GitHub.