conductor-oss/conductor · error · IllegalArgumentException

pruneExecutions: olderThanDays must be >= 1, got ${olderThan

Error message

pruneExecutions: olderThanDays must be >= 1, got ${olderThanDays}

What it means

Thrown by AgentService.computePruneCutoffEpochMs when olderThanDays is less than 1. This guard exists because non-positive values would place the cutoff in the future, matching every terminal execution and causing mass data deletion (documented in issue #1331). The method computes now.minus(olderThanDays, DAYS) and clamps to >= 0. IllegalArgumentException maps to HTTP 400.

Source

Thrown at agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/AgentService.java:605

        workflowService.deleteWorkflow(executionId, archiveTasks);
    }

    /**
     * Computes the prune cutoff, guarding the two ways an unchecked {@code olderThanDays} turned
     * the prune into a data-loss operation (issue #1331): non-positive values put the cutoff in the
     * future (matching every terminal execution), and very large values push the computed epoch
     * negative, which the search backend matched against recent executions. A cutoff clamped to
     * epoch start matches nothing, which is the correct meaning of "older than anything that
     * exists".
     *
     * @param olderThanDays minimum age in days, must be >= 1
     * @param now the current instant
     * @return cutoff in epoch milliseconds, never negative
     */
    @VisibleForTesting
    static long computePruneCutoffEpochMs(int olderThanDays, Instant now) {
        if (olderThanDays < 1) {
            throw new IllegalArgumentException(
                    "pruneExecutions: olderThanDays must be >= 1, got " + olderThanDays);
        }
        return Math.max(0L, now.minus(olderThanDays, ChronoUnit.DAYS).toEpochMilli());
    }

    /**
     * Bulk-delete completed execution records older than {@code olderThanDays} days.
     *
     * <p>Searches for COMPLETED, FAILED, TERMINATED, and TIMED_OUT executions whose end time is
     * before the cutoff, then removes them from the DB in batches.
     *
     * @param olderThanDays minimum age in days for executions to be pruned
     * @param archiveTasks if true, archive task records instead of deleting
     * @return number of executions deleted
     */
    public int pruneExecutions(int olderThanDays, boolean archiveTasks) {
        long cutoffEpochMs = computePruneCutoffEpochMs(olderThanDays, Instant.now());
        String[] terminalStatuses = {"COMPLETED", "FAILED", "TERMINATED", "TIMED_OUT"};

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Pass olderThanDays >= 1 — use a sensible retention like 30 or 90.
  2. If the intent is aggressive cleanup, use 1 (the minimum) rather than 0.
  3. Validate the input at the CLI/API boundary before forwarding to pruneExecutions.

Example fix

// before
agentService.pruneExecutions(0, false); // -> error

// after
int days = Math.max(1, configuredRetentionDays);
agentService.pruneExecutions(days, false);
Defensive patterns

Strategy: validation

Validate before calling

if (olderThanDays < 1) {
    throw new IllegalArgumentException(
        "Retention must be at least 1 day, got " + olderThanDays);
}
agentService.pruneExecutions(olderThanDays, archiveTasks);

Prevention

When it happens

Trigger: Calling pruneExecutions(0, ...) or pruneExecutions(-1, ...); a CLI/API caller passes olderThanDays=0 intending 'delete everything now' not realizing the guard rejects it; a default value of 0 is used when the parameter is omitted.

Common situations: Operator misinterprets 0 as 'no minimum age'; configuration template has an unset default that resolves to 0; automated cleanup job receives a computed days value that underflows to 0 or negative.

Related errors


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/45c349b569d09f7b. Report an issue: GitHub.