{"record":{"id":"eece323ff7d52499","repo":"antiwork/gumroad","slug":"your-profile-was-changed-somewhere-else-please-re","errorCode":null,"errorMessage":"Your profile was changed somewhere else. Please reload the page and try again.","messagePattern":"Your profile was changed somewhere else\\. Please reload the page and try again\\.","errorType":"exception","errorClass":"Settings::ProfileController::StaleProfileError","httpStatus":null,"severity":"warning","filePath":"app/controllers/settings/profile_controller.rb","lineNumber":65,"sourceCode":"    # only inspects a new_record? attachment, so after that reload the dimension and format checks\n    # silently pass and an undersized avatar saves. Checked only when an attachment is actually\n    # pending, to keep unrelated seller validation state out of this path.\n    if current_seller.attachment_changes.any? && !current_seller.valid?\n      return respond_error(current_seller.errors.full_messages.to_sentence)\n    end\n\n    begin\n      current_seller.with_locked_seller_profile do |seller_profile|\n        # Optimistic concurrency: the helper loaded this profile with a locking/current read. Reject\n        # the save if its pages/sections changed elsewhere since this editor loaded. Otherwise this\n        # request would overwrite the layout with a stale snapshot and drop or orphan sections\n        # another session added. A new profile cannot conflict, and design-only saves skip the check.\n        if (permitted_params[:tabs] || permitted_params[:sections]) && seller_profile.persisted?\n          # A persisted profile must be saved against a matching version. A missing/blank version\n          # means the editor loaded before this profile row existed (another session has created it\n          # since), so the submitted layout is stale too. Re-checked here under the lock in case the\n          # layout changed between the early-out above and this transaction.\n          raise StaleProfileError if submitted_version_stale?(seller_profile)\n        end\n        section_ids_by_param_id = {}\n        if permitted_params[:sections]\n          save_service = SellerProfileSections::SaveService.new(seller: current_seller)\n          permitted_params[:sections].each do |section_attributes|\n            section = save_service.upsert!(section_attributes)\n            section_ids_by_param_id[section_attributes[:id]] = section.id\n          end\n        end\n        if permitted_params[:tabs]\n          tabs = permitted_params[:tabs].as_json\n          # Resolve each tab's section references to real db ids, dropping any that no longer\n          # resolve (client GUIDs decrypt to nil) so stale references can't be persisted.\n          all_references_resolved = true\n          tabs.each do |tab|\n            tab[\"sections\"] = Array(tab[\"sections\"]).filter_map do |param_id|\n              resolved_id = section_ids_by_param_id[param_id] || ObfuscateIds.decrypt(param_id)\n              all_references_resolved = false if resolved_id.nil?","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/controllers/settings/profile_controller.rb#L47-L83","documentation":"`StaleProfileError` (settings/profile_controller.rb:4) is raised inside `with_locked_seller_profile` when `submitted_version_stale?` is true: the submitted `profile_version` is blank or differs from `seller_profile.layout_version` (settings/profile_controller.rb:121-123). This is optimistic-locking for the profile layout editor — if tabs/sections changed in another session since this editor loaded, saving would clobber them, so the save is rejected with \"Your profile was changed somewhere else. Please reload the page and try again.\"","triggerScenarios":"POST/PATCH settings profile update including `tabs` or `sections` where: another tab/device saved the profile first (version bumped), the editor loaded before the profile row existed (blank version submitted), or the profile_version hidden field was lost/stale after sitting open. Both the early `stale_layout_submission?` check (line 32) and the authoritative re-check under the lock (line 65) reject the request.","commonSituations":"Same seller editing their profile in two tabs or on phone+desktop; long-lived editor pages saved hours later; browser autofill/extensions dropping the hidden profile_version field; automated clients PUTing full layouts built from an old GET.","solutions":["Reload the profile editor page (fresh layout_version), reapply your changes, and save again.","Close extra tabs editing the same profile so only one editor session is live.","As an API/automation caller: GET the profile immediately before saving, echo its current profile_version in the update payload, and re-fetch + replay on this conflict (standard optimistic-concurrency retry).","Always include `profile_version` when the payload contains `tabs` or `sections` — omitting it is treated as stale by design."],"exampleFix":"# before — submitting a layout captured earlier, no/old version\nput settings_profile_path, params: { tabs: old_tabs, sections: old_sections } # => stale\n\n# after — read-modify-write with the current version, retry on conflict\nbegin\n  version = current_seller.seller_profile.layout_version\n  put settings_profile_path, params: { tabs: new_tabs, sections: new_sections, profile_version: version }\nrescue StaleProfile # 422 with STALE_PROFILE_MESSAGE\n  retry_after_reload\nend","handlingStrategy":"retry","validationCode":"# read-modify-write: fetch current version immediately before saving\nprofile_version = current_seller.seller_profile.reload.layout_version\nput settings_profile_path, params: { tabs:, sections:, profile_version: }","typeGuard":"def fresh_layout_version?(submitted, seller_profile)\n  submitted.present? && seller_profile.layout_version == submitted\nend","tryCatchPattern":"begin\n  update_profile!\nrescue StaleProfileError\n  respond_error(\"Your profile was changed somewhere else. Please reload the page and try again.\")\n  # client: reload, re-apply user's pending edits against the fresh layout, save once more\nend","preventionTips":["Always include the profile_version from the most recent load when saving tabs/sections.","Edit the profile in a single tab; close others before saving.","Automations: GET immediately before PUT and replay edits on version conflict (bounded retries).","Treat this conflict as data protection — do not strip the version field to make it go away."],"tags":["optimistic-locking","concurrency","profile-editor","stale-version","conflict"],"backgroundTag":"optimistic-locking-conflict","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}