{"record":{"id":"4b614220afa4a6c0","repo":"hpyhacking/peatio","slug":"2003","errorCode":"2003","errorMessage":"Failed to cancel order. Reason: #{e}","messagePattern":"Failed to cancel order\\. Reason: #(.+?)","errorType":"exception","errorClass":"APIv2::CancelOrderError","httpStatus":400,"severity":"error","filePath":"app/api/api_v2/orders.rb","lineNumber":67,"sourceCode":"    params do\n      use :auth, :market, :order\n    end\n    post \"/orders\" do\n      order = create_order params\n      present order, with: APIv2::Entities::Order\n    end\n\n    desc 'Cancel an order.', scopes: %w(trade)\n    params do\n      use :auth, :order_id\n    end\n    post \"/order/delete\" do\n      begin\n        order = current_user.orders.find(params[:id])\n        Ordering.new(order).cancel\n        present order, with: APIv2::Entities::Order\n      rescue\n        raise CancelOrderError, $!\n      end\n    end\n\n    desc 'Cancel all my orders.', scopes: %w(trade)\n    params do\n      use :auth\n      optional :side, type: String, values: %w(sell buy), desc: \"If present, only sell orders (asks) or buy orders (bids) will be canncelled.\"\n    end\n    post \"/orders/clear\" do\n      begin\n        orders = current_user.orders.with_state(:wait)\n        if params[:side].present?\n          type = params[:side] == 'sell' ? 'OrderAsk' : 'OrderBid'\n          orders = orders.where(type: type)\n        end\n        orders.each {|o| Ordering.new(o).cancel }\n        present orders, with: APIv2::Entities::Order\n      rescue","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/hpyhacking/peatio/blob/dab8641137c008928c835409342519bfef4eae7f/app/api/api_v2/orders.rb#L49-L85","documentation":"Peatio APIv2 error code 2003 (HTTP 400), raised by POST /api/v2/order/delete. The endpoint runs current_user.orders.find(params[:id]) then Ordering.new(order).cancel, which only publishes a cancel command to the RabbitMQ matching queue (app/services/ordering.rb). A bare rescue catches ANY failure — ActiveRecord::RecordNotFound when the id is not yours or does not exist, or a Bunny/AMQP publish error — and re-raises it as CancelOrderError with the original exception embedded in \"Reason: #{$!}\". Cancelling an order that is already done or cancelled does NOT fail here: the publish still succeeds and the matching engine just logs a skip.","triggerScenarios":"POST /order/delete with an id belonging to another member or a nonexistent id (Reason: Couldn't find Order with id=...); RabbitMQ unreachable or the matching daemon down so AMQPQueue.enqueue raises inside Ordering#cancel; a dropped AMQP channel on the web process.","commonSituations":"Race where the order gets fully filled between the bot's last state poll and the cancel call, followed by a retry with a stale id; duplicate submission of the same cancel; development setups where the web tier runs but the matching daemon does not; signing with the wrong access key so find() scopes to another member's orders.","solutions":["Read the Reason: text — 'Couldn't find Order' means the id is wrong or not yours; a Bunny/AMQP error means the message bus is unreachable.","Verify ownership first with GET /api/v2/order: code 2004 tells you the id is not referenceable under this account.","If the reason is a broker connection error, restore RabbitMQ and the matching daemon, then re-issue the cancel — nothing was applied because the publish failed.","Make the client idempotent: treat a 2003 whose cause is RecordNotFound as 'already gone' success, not as an error."],"exampleFix":"# before\npost '/order/delete', id: id   # 2003 aborts the flow\n\n# after: verify, cancel, treat already-gone as success\nbegin\n  order = get '/order', id: id                      # 2004 => not yours/gone\n  post '/order/delete', id: id if order['state'] == 'wait'\nrescue ApiError => e\n  raise unless e.code == 2003 && e.reason =~ /Couldn't find Order/\n  # order already gone: nothing to cancel\nend","handlingStrategy":"try-catch","validationCode":"# Pre-flight: only cancel orders you own and that are still active\norder = client.get '/api/v2/order', id: id            # 2004 => not yours / gone\nif order['state'] == 'wait'\n  client.post '/api/v2/order/delete', id: id\nend","typeGuard":"// Narrow the parsed error body before deciding\nconst isCancelError = (e: unknown): e is { error: { code: 2003; message: string } } =>\n  typeof e === 'object' && (e as any)?.error?.code === 2003;","tryCatchPattern":"Catch code 2003 and branch on Reason: 'Couldn't find Order' => treat as success (already gone, remove from local state); a Bunny/AMQP connection error => wait for broker recovery and re-issue once; anything else => surface to logs and stop.","preventionTips":["Re-check order state immediately before cancelling; skip done/cancel states instead of blind-firing cancel.","Deduplicate cancel requests client-side so double-submits never reach the API.","In dev, run the matching daemon and RabbitMQ alongside the web tier — cancel is only an AMQP publish.","Log the full Reason: text; it is the original exception and pinpoints the real cause."],"tags":["peatio","apiv2","cancel-order","amqp","rabbitmq","record-not-found"],"backgroundTag":"order-cancel-failed","analyzedSha":"dab8641137c008928c835409342519bfef4eae7f","analyzedAt":"2026-08-23T09:59:18.005Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}