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
- Drop the cursor and restart the log read from offset 0 (or from the beginning without a cursor) to resynchronize with the current file
- If you manage offsets yourself, clamp them to the current file size before requesting a cursor: offset = min(offset, fileSize)
- Avoid copytruncate-style rotation for this log, or rotate by rename so the server re-detects the file via its allowed-name checks
- 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
- Treat any cursor failure after rotation as a signal to restart pagination from offset 0
- Prefer rename-based log rotation over truncation so file sizes never shrink under live offsets
- Always carry the cursor returned by the latest response, never an older one
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
- invalid fingerprint boundary
- invalid log offset
- must be a positive integer
- must be greater than zero
- no file uploaded
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/5e02133758f540d2.
Report an issue: GitHub.