ory/hydra · error

Could not cleanup inactive %s

Error message

Could not cleanup inactive %s

What it means

The janitor's cleanup() wrapper wraps errors from an inner cleanup routine (tables like jti, flow, access, refresh tokens) with the routine name, producing 'Could not cleanup inactive <name>'. The routineName identifies which table/kind of records the failing batch cleanup targets.

Source

Thrown at cmd/cli/handler_janitor.go:176

		switch n {
		case OnlyTokens:
			routines = append(routines, cleanup(out, p.FlushInactiveAccessTokens, "access tokens"))
			routines = append(routines, cleanup(out, p.FlushInactiveRefreshTokens, "refresh tokens"))
		case OnlyRequests:
			routines = append(routines, cleanup(out, p.FlushInactiveLoginConsentRequests, "login-consent requests"))
		case OnlyGrants:
			routines = append(routines, cleanup(out, p.FlushInactiveGrants, "grants"))
		}
	}
	return routines
}

type cleanupRoutine func(ctx context.Context, notAfter time.Time, limit int, batchSize int) error

func cleanup(out io.Writer, cr cleanupRoutine, routineName string) cleanupRoutine {
	return func(ctx context.Context, notAfter time.Time, limit int, batchSize int) error {
		if err := cr(ctx, notAfter, limit, batchSize); err != nil {
			return errors.Wrap(errors.WithStack(err), fmt.Sprintf("Could not cleanup inactive %s", routineName))
		}
		_, _ = fmt.Fprintf(out, "Successfully completed Janitor run on %s\n", routineName)
		return nil
	}
}

func cleanupRun(ctx context.Context, notAfter time.Time, limit int, batchSize int, routines ...cleanupRoutine) error {
	if len(routines) == 0 {
		return errors.New("clean up run received 0 routines")
	}

	for _, r := range routines {
		if err := r(ctx, notAfter, limit, batchSize); err != nil {
			return err
		}
	}
	return nil
}

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Read the wrapped cause (printed via stack trace) to identify the SQL-level failure.
  2. Re-run janitor after verifying database connectivity; janitor is idempotent so partially cleaned data is fine.
  3. Reduce --cleanup-batch-size if statement timeouts occur on very large tables.
  4. Ensure migrations are fully applied (hydra migrate sql up) so the target tables exist.

Example fix

// before
hydra janitor -e "$DSN" --cleanup-batch-size 1000000

// after
hydra migrate sql up -e "$DSN"
hydra janitor -e "$DSN" --cleanup-batch-size 100 --cleanup-grace-period 24h
Defensive patterns

Strategy: retry

Validate before calling

if err := db.PingContext(ctx); err != nil {
    return fmt.Errorf("database unreachable before janitor run: %w", err)
}

Try / catch

err := janitor.Run(ctx)
if err != nil {
    if errors.Is(err, context.Canceled) || isTransientDBError(err) {
        // janitor is idempotent; safe to retry with backoff
        time.Sleep(backoff)
        return janitor.Run(ctx)
    }
    log.Fatalf("janitor failed: %+v", err)
}

Prevention

When it happens

Trigger: Any janitor cleanup routine (e.g. cleanupFlow, cleanupJTI) returning an error while deleting expired records in batches — database connectivity loss mid-run, SQL constraint errors, or context cancellation during a long batched delete.

Common situations: Database connection dropped during a long janitor run on a large table; lock timeouts while the database is under load; batch size too large causing statement timeouts; migrations missing so the table being cleaned doesn't exist.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/31077e95bc1e09e8. Report an issue: GitHub.