.vbw-planning/milestones/ui-fixes-and-smart-scraping/phases/02-feed-reliability/02-PLAN.md
When a user force-fetches a source that is already being fetched (advisory lock busy), the system currently retries 5 times over 2.5 minutes before failing. Instead, force-fetches should fail fast with a clear "Fetch already in progress" message. Scheduled fetches keep the existing retry behavior.
Files to modify:
app/jobs/source_monitor/fetch_feed_job.rbSteps:
retry_on ConcurrencyError declaration (line 11-13) -- we need conditional behaviorrescue_from ConcurrencyError block that checks the force argument:
force: true: log "Fetch already in progress for source #{source_id}", reset fetch_status to previous state (idle or failed), and return without retry. Store the "already in progress" info so the controller can surface it.force: false (scheduled): implement manual retry logic equivalent to the removed retry_on -- retry up to 5 times with 30s wait, using retry_job(wait: 30.seconds) and tracking attempt countupdate_columns(fetch_status: "idle") since the source is already fetching in another process.Files to modify:
lib/source_monitor/fetching/fetch_runner.rbSteps:
FetchRunner.enqueue, when force: true, check if source.fetch_status == "fetching" BEFORE enqueuing the job{ skipped: true, reason: :already_fetching } or just :already_fetchingFiles to modify:
app/controllers/source_monitor/source_retries_controller.rbSteps:
FetchRunner.enqueuerender_fetch_enqueue_response with toast_level: :warning for this caseFiles to create:
test/lib/source_monitor/fetching/force_fetch_lock_test.rbFiles to modify:
test/jobs/source_monitor/fetch_feed_job_test.rb (or wherever job tests live)test/controllers/source_monitor/source_retries_controller_test.rb (or integration test)Steps:
FetchRunner.enqueue(source, force: true) when source.fetch_status == "fetching" returns :already_fetching and does NOT enqueue a jobFetchRunner.enqueue(source, force: true) when source.fetch_status == "idle" enqueues normallyFetchRunner.enqueue(source, force: false) always enqueues regardless of statusbin/rubocop passes with zero offensesbin/rails test passes