ruvnet/ruflo · warning · Error

Claim is still in grace period

Error message

Claim is still in grace period

What it means

Plain Error thrown by markStealable when isInGracePeriod(claim) is true. Newly created claims are protected by a grace window (configured in WorkStealingConfig) so agents are not relieved of work the instant they claim it. The claim must age past the grace period before it can be marked stealable.

Source

Thrown at v3/@claude-flow/claims/src/application/work-stealing-service.ts:221

  /**
   * Mark work as stealable with the given reason
   */
  async markStealable(issueId: IssueId, info: StealableInfo): Promise<void> {
    const claim = await this.repository.findByIssueId(issueId);

    if (!claim) {
      throw new Error(`Claim not found for issue: ${issueId}`);
    }

    // Check if already stealable
    if (claim.stealInfo) {
      return; // Already marked
    }

    // Check grace period protection
    if (this.isInGracePeriod(claim)) {
      throw new Error(`Claim is still in grace period`);
    }

    // Check progress protection
    if (this.isProtectedByProgress(claim)) {
      throw new Error(`Claim is protected by progress (${claim.progress}%)`);
    }

    // Update claim with stealable info
    const now = new Date();
    claim.stealInfo = {
      ...info,
      markedAt: now,
    };
    claim.stealableAt = now;

    await this.repository.update(claim);

    // Emit event

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Wait until the grace window elapses before retrying markStealable.
  2. Lower config.gracePeriodMinutes (or equivalent field in DEFAULT_WORK_STEALING_CONFIG) if the default is too conservative for your workload.
  3. Schedule the mark for now + remaining grace time instead of polling in a tight loop.
  4. In auto-mark loops, skip claims still in grace rather than treating them as errors.

Example fix

// before
await workStealing.markStealable(issueId, info); // freshly claimed -> throws

// after
const claim = await repo.findByIssueId(issueId);
const graceMs = config.gracePeriodMinutes * 60_000;
if (Date.now() - claim.createdAt.getTime() >= graceMs) {
  await workStealing.markStealable(issueId, info);
} else {
  scheduleForLater(issueId, graceMs - (Date.now() - claim.createdAt.getTime()));
}
Defensive patterns

Strategy: retry

Validate before calling

const claim = await repository.findByIssueId(issueId);
const elapsedMs = Date.now() - (claim?.createdAt?.getTime() ?? 0);
const graceMs = config.gracePeriodMinutes * 60_000;
if (elapsedMs < graceMs) {
  // not eligible yet; schedule for later
  scheduleMark(issueId, graceMs - elapsedMs);
  return;
}
await workStealing.markStealable(issueId, info);

Try / catch

try {
  await workStealing.markStealable(issueId, info);
} catch (e) {
  if (e instanceof Error && /grace period/.test(e.message)) {
    // transient: retry after the remaining grace window
    return scheduleMark(issueId, retryAfterMs);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling markStealable immediately or very soon after a claim is created; an auto-detect loop whose interval is shorter than the grace period; gracePeriod misconfigured to a large value.

Common situations: Grace period set too long relative to expected task duration; tight polling loop; clock skew when an HLC is wired in (nowMs uses hlc.physicalMs).

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/ba8fd85e7fd17cbf. Report an issue: GitHub.