{"record":{"id":"fb1378fccd546788","repo":"we-promise/sure","slug":"import-session-conflict","errorCode":"import_session_conflict","errorMessage":"client_session_id already exists with a different expected_chunks value","messagePattern":"client_session_id already exists with a different expected_chunks value","errorType":"http","errorClass":"ImportSession::ConflictError","httpStatus":409,"severity":"error","filePath":"app/models/import_session.rb","lineNumber":118,"sourceCode":"    end\n  end\n\n  def self.create_or_find_for!(family:, import_type:, client_session_id:, expected_chunks:)\n    import_type = import_type.presence || \"SureImport\"\n    expected_chunks = normalize_positive_integer(expected_chunks)\n    unless IMPORT_TYPES.include?(import_type)\n      session = new(import_type: import_type)\n      session.errors.add(:import_type, \"must be SureImport\")\n      raise ActiveRecord::RecordInvalid.new(session)\n    end\n\n    if client_session_id.present?\n      session = family.import_sessions.find_or_initialize_by(client_session_id: client_session_id)\n      if session.persisted? &&\n         expected_chunks.present? &&\n         session.expected_chunks.present? &&\n         session.expected_chunks != expected_chunks\n        raise ConflictError, \"client_session_id already exists with a different expected_chunks value\"\n      end\n    else\n      session = family.import_sessions.build\n    end\n\n    session.import_type = import_type\n    session.expected_chunks ||= expected_chunks\n    session.save!\n    session\n  rescue ActiveRecord::RecordNotUnique\n    raise unless client_session_id.present?\n\n    existing = family.import_sessions.find_by(client_session_id: client_session_id)\n    raise unless existing\n\n    if expected_chunks.present? &&\n       existing.expected_chunks.present? &&\n       existing.expected_chunks != expected_chunks","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/import_session.rb#L100-L136","documentation":"ImportSession.create_or_find_for! (app/models/import_session.rb:103-119) turns a create/reconnect request into HTTP 409 {error: 'import_session_conflict'} when the family already has a persisted session with this client_session_id, both the stored and the newly sent expected_chunks are present, and they differ. client_session_id is the idempotency key of a chunked SureImport upload, and expected_chunks is frozen on first use so the server can later validate that sequences 1..N are all present before publishing.","triggerScenarios":"POST /api/v1/import_sessions with the same client_session_id twice — first with expected_chunks=5, then expected_chunks=3 — e.g. the client re-split the file into fewer chunks and reused the session id.","commonSituations":"Client re-splits the NDJSON after a partial failure and sends a new chunk count under the old id; stale local state after a reinstall; two devices resuming the same session with different chunk plans.","solutions":["Reuse the original expected_chunks value stored alongside client_session_id on the client","Omit expected_chunks on reconnect — the session keeps its stored value and the conflict check is skipped","If the chunk plan genuinely changed, start a new client_session_id (chunks from the old session do not carry over)","GET the existing session and read its expected_chunks before re-sending the create"],"exampleFix":"# before\nPOST /api/v1/import_sessions\n{ \"type\": \"SureImport\", \"client_session_id\": \"job-42\", \"expected_chunks\": 3 }\n# → 409: session job-42 was created with expected_chunks=5\n\n# after\n{ \"type\": \"SureImport\", \"client_session_id\": \"job-42\", \"expected_chunks\": 5 }","handlingStrategy":"validation","validationCode":"plan = load_upload_plan # persisted {client_session_id:, expected_chunks:} from the first attempt\npayload = {\n  type: 'SureImport',\n  client_session_id: plan[:client_session_id],\n  expected_chunks: plan[:expected_chunks] # same value as the original create\n}\napi.post('/api/v1/import_sessions', payload)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Persist client_session_id together with expected_chunks and always send the pair unchanged","Omit expected_chunks on reconnect — the stored value wins and no conflict check runs","Changed the chunk plan? Mint a new client_session_id and accept the fresh start"],"tags":["rails","import-session","idempotency","chunked-upload","http-409"],"backgroundTag":"idempotency-key-conflict","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}