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 eventView on GitHub (pinned to 6b01dc5a68)
Solutions
- Wait until the grace window elapses before retrying markStealable.
- Lower config.gracePeriodMinutes (or equivalent field in DEFAULT_WORK_STEALING_CONFIG) if the default is too conservative for your workload.
- Schedule the mark for now + remaining grace time instead of polling in a tight loop.
- 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
- Set the auto-detect interval longer than config.gracePeriodMinutes.
- Compute eligibility from claim.createdAt before attempting to mark.
- Account for HLC physicalMs vs wall-clock when in federated mode.
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
- Claim not found for issue: ${issueId}
- Claim is protected by progress (${claim.progress}%)
- No steal to contest - issue was not recently stolen
- Contest has already been resolved
- Contest window has expired
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/ba8fd85e7fd17cbf.
Report an issue: GitHub.