multica-ai/multica · warning
move anchors are too close; refresh and retry
Error message
move anchors are too close; refresh and retry
What it means
issueMovePosition places the moved issue at the midpoint between the two anchors: position = before + (after-before)/2. After repeated inserts into the same gap, floating-point precision runs out and the midpoint can equal an anchor or become Inf/NaN; the check position > before && position < after catches this. The error tells the client the gap is exhausted and it must refresh so the server (or a rebalance) can re-space positions.
Source
Thrown at server/internal/handler/issue_move.go:199
writeError(w, http.StatusBadRequest, "move anchor not found in this workspace")
} else {
writeIssueTableQueryFailure(w, r, "failed to resolve move anchor")
}
return nil, false
}
return &position, true
}
func issueMovePosition(current float64, before, after *float64) (float64, error) {
switch {
case before != nil && after != nil:
if !(*before < *after) {
return 0, errors.New("move anchors are stale or out of order")
}
position := *before + (*after-*before)/2
if !(position > *before && position < *after) ||
math.IsInf(position, 0) || math.IsNaN(position) {
return 0, errors.New("move anchors are too close; refresh and retry")
}
return position, nil
case before != nil:
position := *before + 1
if math.IsInf(position, 0) || math.IsNaN(position) {
return 0, errors.New("move position is out of range")
}
return position, nil
case after != nil:
position := *after - 1
if math.IsInf(position, 0) || math.IsNaN(position) {
return 0, errors.New("move position is out of range")
}
return position, nil
default:
return current, nil
}
}View on GitHub (pinned to 2c0912b6ec)
Solutions
- On this error, refetch the list and retry once — fresh anchors will have a usable gap (and any rebalancing will have re-spaced values).
- Prefer moving to the very top/bottom (single-anchor +1/-1 form) instead of repeatedly subdividing one gap.
- If it recurs constantly for the same gap, trigger/request a position rebalance for the list or spread inserts across neighboring gaps.
- Client-side, avoid issuing a move whose anchors' positions are already adjacent in float terms.
Example fix
// before
async function drag(issueId, beforePos, afterPos) {
return api.move(issueId, {before: beforePos, after: afterPos});
}
// after
async function drag(issueId, beforeId, afterId) {
try { return await api.move(issueId, {before: beforeId, after: afterId}); }
catch (e) {
if (e.message.includes('too close')) {
await refetchList(); // get re-spaced positions
return api.move(issueId, {before: beforeId, after: afterId}); // one retry
}
throw e;
}
} Defensive patterns
Strategy: retry
Validate before calling
function gapIsUsable(beforePos, afterPos) {
const mid = beforePos + (afterPos - beforePos) / 2;
return mid > beforePos && mid < afterPos && Number.isFinite(mid);
}
// skip the request if the local gap is already exhausted
if (before && after && !gapIsUsable(before.position, after.position)) {
await refetchIssues(); // get re-spaced positions before attempting the move
} Type guard
const hasFloatRoom = (a, b) => b > a && (a + (b - a) / 2) < b;
Try / catch
try {
await api.moveIssue(issueId, { beforeId, afterId });
} catch (e) {
if (e.status === 400 && /too close/.test(e.message)) {
await refetchIssues(); // server-side re-spacing makes the gap usable again
await api.moveIssue(issueId, { beforeId, afterId });
} else {
throw e;
}
} Prevention
- Prefer top/bottom drops (single anchor) over repeatedly subdividing the same gap.
- Check gap usability locally before sending, using the same midpoint math as the server.
- If a specific gap keeps failing, spread inserts into neighboring gaps or request a list rebalance.
When it happens
Trigger: Drag-and-drop the same narrow gap hundreds of times so consecutive float64 positions differ by less than one ULP; the midpoint computation rounds to before or after exactly, failing the strict inequality; mathematically (after-before)/2 underflows to 0 at extreme magnitudes.
Common situations: Long-lived boards where users always insert at the same spot (top of list) without full reordering; test suites performing thousands of inserts into one gap; position values grown large after many +1/-1 appends (precision at 2^53 scale).
Related errors
- move anchors are stale or out of order
- cloud pat verifier unavailable
- ErrRepoBusy
- move position is out of range
- import source temporarily unavailable
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/6dec1ee78c74631c.
Report an issue: GitHub.