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

  private

View on GitHub (pinned to e69894adb9)

Solutions

  1. Guard with return unless pledge.reload.status_open? before calling cancel!
  2. Rescue GoalPledge::NotOpenError in controllers/jobs and treat it as an idempotent no-op
  3. 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

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.