we-promise/sure · warning · GoalPledge::NotOpenError
Only open pledges can be cancelled
Error message
Only open pledges can be cancelled
What it means
GoalPledge#cancel! raises NotOpenError unless status_open?. Cancellation is only defined for open pledges; matched, already-cancelled, or expired pledges cannot be cancelled again.
Source
Thrown at app/models/goal_pledge.rb:127
update!(status: "matched")
end
end
class NotOpenError < StandardError; end
# Raised when a Transaction is already claimed by a different open
# pledge. Lets the reconciler distinguish a known race ("another worker
# got there first") from a generic validation failure.
class AlreadyClaimedError < StandardError; end
def extend!(days: EXTEND_DAYS)
raise NotOpenError, "Only open pledges can be extended" unless status_open?
update!(expires_at: expires_at + days.days)
end
def cancel!
raise NotOpenError, "Only open pledges can be cancelled" unless status_open?
update!(status: "cancelled")
end
def expire!
return unless status_open?
update!(status: "expired")
end
def days_left
return 0 unless status_open?
delta = ((expires_at - Time.current) / 1.day).ceil
[ delta, 0 ].max
end
privateView on GitHub (pinned to e69894adb9)
Solutions
- Guard with return unless pledge.reload.status_open? before calling cancel!
- Rescue GoalPledge::NotOpenError in controllers/jobs and treat it as an idempotent no-op
- Reflect current pledge status in the UI before offering the cancel action
Example fix
// before pledge.cancel! // after pledge.reload pledge.cancel! if pledge.status_open?
Defensive patterns
Strategy: validation
Validate before calling
pledge.reload.status_open? # false => cancel! will raise NotOpenError
Type guard
def cancellable?(pledge) = pledge.reload.status_open? # narrow before calling cancel!
Try / catch
rescue GoalPledge::NotOpenError in cancel endpoints/jobs and return idempotent success (the end state — not open — is what the caller wanted)
Prevention
- Make cancel actions idempotent; a second cancel of a cancelled pledge is not an error
- Reload before acting in UI flows to avoid stale-status actions
- Treat NotOpenError as expected concurrency, matching the reconciler's pattern
When it happens
Trigger: Calling cancel! on a pledge whose status is anything but open — e.g. a cancel request racing the reconciler that just matched the pledge, or a duplicate cancel submission.
Common situations: User clicks cancel after an incoming transaction matched the pledge; double-submit of the cancel action; stale UI showing an already-cancelled pledge as open.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/6f395fa75e82f5c2.
Report an issue: GitHub.