Skip to content

Take the cluster lock wait classification fix that gets main green (harper#2685) - #870

Merged
kriszyp merged 1 commit into
mainfrom
fix/cluster-lock-wait-reports-contention
Sep 18, 2026
Merged

kriszyp merged 1 commit into
mainfrom
fix/cluster-lock-wait-reports-contention

Conversation

@kriszyp

@kriszyp kriszyp commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

Moves the core pointer to take harper#2685, which gets main green. The only change here is the core gitlink: 35606037 (v5.3.0-beta.1) → c43530ff0, that release commit plus the seven commits of #2685 and nothing else. No other harper-pro file moves with it.

What was red

main went green → red at f5f10704b (Integration Tests run 35364306818), on Cluster Integration Tests 1/6 (Node.js v24) only — the same shard passed on Node v22 and v26.5.0:

test at integrationTests/cluster/recordLockCluster.test.mjs:168
✖ a lock is exclusive across nodes while held, and hands over through recall on release
  AssertionError: a second node must wait, then time out:
  {"code":"LOCK_UNAVAILABLE","title":"… the key's home answered timeout","status":503}
  503 !== 423

The release commit is not the culprit. It bumps a version string and the core pointer; that core range touches resources/indexes/hnswDerivedIndex.ts, a TTL integration test, the unit-test workflow and DESIGN.md — nothing on the record-lock path. The test itself arrived ~90 minutes earlier in #822 and had run four times on main: three green, one red.

One leg is usually a flake, but the outcome here was not random. The test picks 'exclusive-' + Date.now(), so homeFor() re-rolls which of the three nodes homes the key on every run: when the contender was itself the home the wait answered 423 every time (#grantLocally is synchronous, no timeout race), and when the home was remote it answered 503 every time. Three green runs then a red one, on unchanged code, is that re-roll.

In core's RecordLockCoordinator.acquire() the terminal 423-vs-503 choice came from the most recent delegation reply, and the retry loop is built so that reply is systematically the one its own deadline truncated: after a contended denial it slept min(retryAfterMs, remaining) — which when remaining <= 25 consumes the deadline exactly — then looped with no deadline check and sent a probe clamped to a 1 ms budget, which can only return the synthetic reason: 'timeout'. So a wait that watched the key held for its whole budget answered 503. #2685 has the full analysis and the fix.

This matters beyond CI: 423 tells a client "held, retry"; 503 LOCK_UNAVAILABLE tells it cluster lock coordination is broken.

For the human reviewer

  • Is a core bump the right shape for this at all? The alternative was to revert the merge that introduced the failing test (#822) — the dispatch brief explicitly allows it, on the grounds that main being green is worth more than the change being preserved. Declined on a fact: the test is correct and the production code is wrong, so reverting would remove the only thing that detected a client-visible defect and leave lock() returning 423-or-503 at random for a contended key. Loosening the test's assertion would do the same. The change is one submodule pointer, which is the smallest shape that can carry a core fix.

  • Declined — "skipping the final remote probe loses an acquisition a release inside the remaining window would have granted." It does not, for a remote home. The skipped pass runs only when the backoff already reached the deadline, so it would have gone out with Math.max(1, remaining) = 1 ms of budget and could only have returned this node's own synthetic timeout. The local-home case is different — #grantLocally is synchronous and needs no budget — and is explicitly exempted from the early exit for exactly that reason. The real cost is that a remote contended wait now returns its 423 up to retryAfterMs (today always 25 ms) earlier than the caller's timeout.

  • Open decision — contended followed by home silence ends 423, not 503. A home detected unreachable already throws 503 from #requestRemotely's catch; reason: 'timeout' means only that this node stopped waiting, and a sub-second wait cannot tell a down home from a slow one. A later answer does override — contended → not-home ends 503 — because that is a fact the home stated; silence is not. Equally defensible the other way, and a one-line reversal.

  • Merge order is load-bearing. Depends-on: harper#2685 below is what companion-check reads; without it the gate returns success immediately and this pointer could merge while #2685 is still open, leaving core on a commit that is not on harper main.

Verification

  • Core: test:unit:resources:core (the CI gate) 2497 passing, 0 failing, with seven new cases each checked to fail with its specific fix reverted. Details and the table are in #2685.
  • Here: the gitlink is the whole diff, so the verification that matters is this PR's own Cluster Integration Tests shard running integrationTests/cluster/recordLockCluster.test.mjs against the bumped core. All nine integration shards pass, Cluster Integration Tests 1/6 (Node.js v24) included — the shard that was red. That is the end-to-end evidence for the fix.
  • Scope of that evidence: a PR run builds on v22/v24/v26.5.0 but runs the integration shards on v24 only; the v22 and v26.5.0 integration legs run on push to main. v24 is the leg that failed, and the defect is version-independent, so this covers it — but the other two legs are not exercised until merge.
  • The failing suite could not be run locally: an unrelated concurrently-running Harper instance on the build box held *:9925, which validateLoopbackAddressPool reports as all 32 pool addresses being in use.

Depends-on: harper#2685

Refs #822

🤖 Generated with Claude Code

Origin — the dispatch brief this PR was written from

harper-pro main is red: Integration Tests at f5f1070

main on HarperFast/harper-pro went from green to red. Failing workflows: Integration Tests at f5f1070.

Find the merge that broke it and get main green. Walk that workflow's recent push runs on main oldest-to-newest to find the FIRST red head SHA and the PR it came from, then pull the failing test names from that run and from the current head. A failure on every matrix leg (Node version, Bun, uWS, Windows) is a regression; one leg is usually a flake.

Then either fix it or, when the culprit is a single merge and the fix is not obvious, open a revert and say so — main being green is worth more than the change being preserved. Record the fingerprint on the initiatives/ci-health board doc either way (prose row beginning with the test path, never a bare ref), and add your PR to initiatives/testing-and-deploy > "CI & build infrastructure" as a bare-ref line so it renders as a card.

Dispatch: task main-red-kriszyp_harper-pro_f5f10704b_f0efc59e · queued by automation · ran by claude/opus/high · worker kzyp-xps-1

Review-Coverage: authored=claude; ran=gemini,cursor-composer,codex; adjudicated=domain; declined=cursor-grok; rounds=2; full=1 @ c075dba

Human-Review-Need: 3 (decisions: deadline-silence-after-contention-is-423, skip-terminal-remote-probe) @ c075dba

…arper#2685)

`integrationTests/cluster/recordLockCluster.test.mjs` "a lock is exclusive
across nodes while held" asserted 423 and got 503 LOCK_UNAVAILABLE
"the key's home answered timeout", which turned main red at f5f1070.

The defect is in core's `RecordLockCoordinator.acquire()`: it chose 423 vs 503
from the most recent delegation reply, and the retry loop is built so that reply
is systematically the one its own deadline truncated. The fix classifies from an
answer a home actually gave, under the generation current when the wait ends.

Depends-on: harper#2685

Dispatch-Task: main-red-kriszyp_harper-pro_f5f10704b_f0efc59e
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gemini-code-assist[bot]

This comment was marked as resolved.

@kriszyp
kriszyp marked this pull request as ready for review September 18, 2026 23:28
@kriszyp
kriszyp requested a review from a team as a code owner September 18, 2026 23:28
@kriszyp
kriszyp merged commit d1cd088 into main Sep 18, 2026
47 checks passed
@kriszyp
kriszyp deleted the fix/cluster-lock-wait-reports-contention branch September 18, 2026 23:28
@claude

claude Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found. Entire diff is a core gitlink bump — no reviewable code surface in this repo (the actual fix is in harper#2685).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant