JuliusBrussee/caveman · error

CAVEMAN_CCR_MAX_BYTES must be a positive integer

Error message

CAVEMAN_CCR_MAX_BYTES must be a positive integer

What it means

The CAVEMAN_CCR_MAX_BYTES environment variable was set but could not be parsed as a positive 64-bit integer, or parsed to a value <= 0. Open rejects the configuration up front rather than falling back to a default, so a bad env var fails loudly instead of silently changing the budget.

Solutions

  1. Set CAVEMAN_CCR_MAX_BYTES to a plain positive integer of bytes, e.g. 104857600 for 100 MiB.
  2. Unset the variable entirely to use DefaultMaxStorageBytes if you did not intend to override the budget.
  3. Remove unit suffixes, underscores, thousands separators, and surrounding quotes from the value.
  4. Convert human sizes before export: 50MB -> 52428800.
  5. Check the deployment config/secret for placeholder values like "<max-bytes>" or "changeme".

Example fix

# before
export CAVEMAN_CCR_MAX_BYTES=100MB
# after
export CAVEMAN_CCR_MAX_BYTES=104857600
Defensive patterns

Strategy: validation

Validate before calling

raw := strings.TrimSpace(os.Getenv("CAVEMAN_CCR_MAX_BYTES"))
if raw != "" {
    if n, err := strconv.ParseInt(raw, 10, 64); err != nil || n <= 0 {
        return fmt.Errorf("CAVEMAN_CCR_MAX_BYTES must be a positive integer, got %q", raw)
    }
}

Prevention

When it happens

Trigger: ccr.Open(path) is called with CAVEMAN_CCR_MAX_BYTES set to something like "abc", "", after TrimSpace it is non-empty but non-numeric (e.g. "10MB", "1_000"), negative ("-5"), or zero ("0"), or a value above int64 range ("99999999999999999999").

Common situations: Operator writes a human-readable size like "100MB" instead of raw bytes; quotes or whitespace/unit suffixes copied from docs; value 0 intended to mean 'unlimited'; CI secrets misconfigured with a placeholder string.

Understand the failure class

Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/3c1087b6a3b33f3b. Report an issue: GitHub.

Appendix: source

Thrown at engine/ccr/store_sqlite.go:143

	return err
}

func isBusy(err error) bool {
	if err == nil {
		return false
	}
	s := err.Error()
	return strings.Contains(s, "database is locked") || strings.Contains(s, "SQLITE_BUSY")
}

// Open opens (creating if needed) the SQLite database at path and migrates the
// schema. Use ":memory:" for an ephemeral store (tests, the eval harness).
func Open(path string) (*Store, error) {
	maxBytes := DefaultMaxStorageBytes
	if raw := strings.TrimSpace(os.Getenv("CAVEMAN_CCR_MAX_BYTES")); raw != "" {
		parsed, err := strconv.ParseInt(raw, 10, 64)
		if err != nil || parsed <= 0 {
			return nil, fmt.Errorf("CAVEMAN_CCR_MAX_BYTES must be a positive integer")
		}
		maxBytes = parsed
	}
	return OpenWithBudget(path, maxBytes)
}

// OpenWithBudget opens a persistent store with an explicit retained-payload
// budget. New writes fail with ErrBudgetExceeded; existing recovery rows are
// never evicted because an emitted transformed request may still reference any
// handle.
func OpenWithBudget(path string, maxBytes int64) (*Store, error) {
	return openWithBudget(path, maxBytes, nil)
}

func openWithBudget(path string, maxBytes int64, afterPrepare func()) (*Store, error) {
	return openWithBudgetHooks(path, maxBytes, afterPrepare, nil)
}

View on GitHub (pinned to 3ee70a1026)