router-for-me/CLIProxyAPI · error

invalid cursor offset

Error message

invalid cursor offset

What it means

Returned by newLogCursor (logs.go:980) when the requested cursor offset is negative or larger than the current size of the log file. Cursors embed a byte offset; before issuing a new cursor the server verifies the offset still falls within [0, fileSize]. If the file shrank since the offset was obtained (rotation, truncation), the check fails.

Source

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

	if errRel != nil {
		return "", fmt.Errorf("resolve log file: %w", errRel)
	}
	if rel == "." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) || rel == ".." || filepath.IsAbs(rel) {
		return "", fmt.Errorf("invalid log file")
	}
	return fullPath, nil
}

func newLogCursor(path string, offset, latest int64) (string, error) {
	info, errStat := os.Stat(path)
	if errStat != nil {
		return "", errStat
	}
	if info.IsDir() {
		return "", fmt.Errorf("invalid log file")
	}
	if offset < 0 || offset > info.Size() {
		return "", fmt.Errorf("invalid cursor offset")
	}
	fingerprintCursor := logCursor{
		Offset: offset,
		Size:   info.Size(),
	}
	fingerprint, errFingerprint := logFileFingerprint(path, cursorFingerprintBoundary(fingerprintCursor))
	if errFingerprint != nil {
		return "", errFingerprint
	}
	return encodeLogCursor(logCursor{
		Version:         logCursorVersion,
		File:            filepath.Base(path),
		Offset:          offset,
		Size:            info.Size(),
		ModTime:         info.ModTime().Unix(),
		ModTimeUnixNano: info.ModTime().UnixNano(),
		LatestTimestamp: latest,
		Fingerprint:     fingerprint,

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Drop the cursor and restart the log read from offset 0 (or from the beginning without a cursor) to resynchronize with the current file
  2. If you manage offsets yourself, clamp them to the current file size before requesting a cursor: offset = min(offset, fileSize)
  3. Avoid copytruncate-style rotation for this log, or rotate by rename so the server re-detects the file via its allowed-name checks
  4. Verify the cursor you replay actually came from the most recent response, not from an earlier session

Example fix

// before: replaying a stale offset after the file shrank
cursor, err := newLogCursor(path, oldOffset, latest)

// after: clamp to current size before creating the cursor
info, err := os.Stat(path)
if err != nil {
	return err
}
if oldOffset > info.Size() {
	oldOffset = 0 // file rotated/truncated; restart
}
cursor, err := newLogCursor(path, oldOffset, latest)
Defensive patterns

Strategy: validation

Validate before calling

// Clamp the offset to the current file size before requesting a cursor.
info, err := os.Stat(path)
if err != nil {
	return err
}
if offset < 0 || offset > info.Size() {
	offset = 0 // file rotated/truncated; restart from the beginning
}

Prevention

When it happens

Trigger: Calling the log pagination flow with an offset derived from a previous read after the log file was truncated or rotated to a smaller file; passing a negative offset programmatically; two clients racing where one rotates the file between the other's read and continuation.

Common situations: External log rotation (logrotate with copytruncate) shrinks the active log while a dashboard keeps an old cursor; a long-lived tail client resumes after disk cleanup truncated logs.

Related errors


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