Repository navigation
fix(replication): report which peers failed instead of a failed operation - #1755
dawsontoth wants to merge 1 commit into
Conversation
…tion Harper applies a replicated operation on the node Studio called before fanning out, and `replicated` lists only the peers. When the lone peer (or every peer) failed, Studio said "The operation failed on the single node" / "on all N nodes", although the node it talked to had already applied the change. Production RUM shows this on file saves whose one peer dropped TLS. The message now names only what the response establishes: replication to N of M peers failed. The request still rejects; only the message changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the replication failure error messages in rejectReplicationFailures to clarify that the failures occurred on peer nodes rather than the called node itself, and updates the corresponding unit tests. Feedback suggests simplifying the nested ternary operator and removing a redundant pluralize call in the error message construction for better readability.
| const failedPeers = failures.length === peerCount | ||
| ? peerCount === 1 ? 'the peer node' : `all ${peerCount} peer nodes` | ||
| : `${failures.length} of ${pluralize(peerCount, 'peer node', 'peer nodes')}`; |
There was a problem hiding this comment.
The nested ternary operator here can be simplified for better readability. Additionally, in the else branch (where failures.length !== peerCount), peerCount is guaranteed to be at least 2 (since failures.length >= 1 and failures.length < peerCount). Therefore, the call to pluralize is redundant because the plural form 'peer nodes' will always be used. We can simplify this to a direct string concatenation.
| const failedPeers = failures.length === peerCount | |
| ? peerCount === 1 ? 'the peer node' : `all ${peerCount} peer nodes` | |
| : `${failures.length} of ${pluralize(peerCount, 'peer node', 'peer nodes')}`; | |
| const failedPeers = failures.length === peerCount | |
| ? (peerCount === 1 ? 'the peer node' : 'all ' + peerCount + ' peer nodes') | |
| : failures.length + ' of ' + peerCount + ' peer nodes'; |
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||
When a replicated operation failed on the node's only peer, or on every peer, Studio's toast said "The operation failed on the single node" or "…on all N nodes". Both claims are false. Harper applies the operation on the node Studio called before it fans out (harper
components/operations.js:1396-1402forset_component_file, and the same order for DDL,add_component,drop_componentandset_configuration). harper-pro'sreplicateOperationfillsreplicated[]fromserver.nodes, which lists only the peers (replication/replicator.ts:867-893at harper-proa6fde69).rejectReplicationFailuresnow reports only what the response shows:Failed to replicate to the peer node:,…to all 3 peer nodes:, or…to 1 of 2 peer nodes:, followed by the per-node reasons as before.Found in the 2026-09-25 daily RUM review: 7 events in 2 sessions over the last 2 days, all file saves on the apps view where the one peer answered
Client network socket disconnected before secure TLS connection was established. None appear earlier in the 30-day window.The request still rejects on any peer failure. Only the message changed.
For the human reviewer
CreateNewTableModalnever runsonSuccess, sodescribe_allis not invalidated, the modal stays open, and a resubmit fails with "already exists". Delete Table, Add SSH Key and file saves behave the same way. Resolving with a warning toast would change the contract for every caller of the interceptor, so it needs its own design pass. I kept it out of this PR on purpose.replicated[]excluding the called node. I traced this in harper-pro (above) and confirmed that central-manager's/Cluster/{id}/operationproxy passes the body through without touchingreplicated. The onlyreplicatedhit in itssrcis an unrelated comment.restart_servicebuilds its ownreplicatedentries withoutstatus: 'failed'(harperbin/restart.ts:259-305), so it never reaches this branch.Verification
src/integrations/api/replication.test.ts: the four rejection cases are updated, one per branch (the only peer, all peers, 1 of 2, 2 of 3). Against the oldreplication.tsall four fail (fails-on-base check).vitest run365 files, 3,334 passed;tsc -b,oxlint,dprint checkall clean. The pre-commit hook also ran the full gate on the final commit.Complexity: easy
🤖 Generated with Claude Code
Review-Coverage: authored=claude; ran=gemini,cursor-grok; adjudicated=domain; blocked=codex(model-unavailable); declined=cursor-composer,cursor-kimi,cursor-muse; rounds=3 @ d4b55da
Human-Review-Need: 4 (decisions: partial-peer-failure-rejects, origin-excluded-count, peer-node-vocabulary) @ d4b55da