Skip to content

Campaign falsely marked COMPLETED mid-progress (root cause of #14, still reproduces on v2.3.0) #18

Description

@1p0d

Version: v2.3.0 (ee5444b, identical to main as of 2026-09-11), Docker, linux/arm64

What happens

A multi-drop campaign is marked COMPLETED and dropped from farming while Twitch still has it in progress. Observed on WARDOGS Early Access 0.1 (6c1160a5-10df-40bc-934a-eae97437b3ab, 9 drops from 15 to 900 minutes):

  • Dashboard: COMPLETED, 647/660 · 98%, 6/9 received
  • Twitch inventory at the same time: still in dropCampaignsInProgress, three drops unclaimed (660, 780 and 900 minutes)
  • The ID is written to completed_campaigns, so the campaign stays skipped from then on

This looks like the same bug as #14 (OWCS MSC Day 2), which was closed as completed but still reproduces.

Evidence

Log times are CEST. The game has a second campaign, WARDOGS Beta & Launch (aa82829e-9f3c-4572-a120-39d2eb36729b), with one 30-minute drop WARDOG (2f7cc39d-a6f7-11f1-96ea-0a58a9feac02), which had already been claimed.

While the pick was farming Early Access 0.1, DropCurrentSessionContext kept returning the WARDOG drop ID, paired with Early Access's minute count:

[Drops/Watch] <channel> drop=2f7cc39d mins=648/30
[Drops/Poll] drop complete on campaign 6c1160a5-10df-40bc-934a-eae97437b3ab (648/30)

That "drop complete" line was logged 582 times between 01:23 and 11:45, once per poll. Each one ran MarkCompletedIfFinishedExternally, which correctly refused to mark the campaign while it was still in the inventory, until one inventory response came back empty:

11:45:31 [Drops/Merge] Dashboard=111 campaigns, Inventory=0 campaigns, Details fetched=95/95, gameEventDrops=0 benefits
11:45:31 [Drops] Campaign "Early Access 0.1" finished externally (poll: complete + not in inventory) — marked completed
11:45:40 [Drops/Merge] Dashboard=111 campaigns, Inventory=5 campaigns, Details fetched=92/92, gameEventDrops=9 benefits

That was the only empty inventory in about 1,230 fetches that day. Nine seconds later it was back to normal.

Root cause

Two defects combine.

1. The poll uses the session's drop without checking that it belongs to the picked campaign. PollProgressOnce (internal/drops/progress.go:103) applies session.DropID and session.RequiredMinutesWatched as progress for pickedCampID. At :119 it treats current >= required as "the picked campaign's drop is done" and calls MarkCompletedIfFinishedExternally(pickedCampID) (:125). When the session names a drop from a sibling campaign of the same game, the comparison uses the wrong drop's requirement: 30 minutes instead of 660.

2. A failed inventory fetch looks the same as "nothing in progress". GetDropsInventory throws away the inventory error (internal/twitch/drops.go:214: inventoryCampaigns, claimedBenefits, _ := g.getDropsFromInventory()), and getDropsFromInventory returns nil, nil, nil when the response has no currentUser or inventory (:553-568). Every campaign then reads InInventory=false, which is exactly what MarkCompletedIfFinishedExternally (internal/drops/inventory.go:152) treats as "finished". Nothing is logged.

Defect 1 turns the rare glitch in defect 2 into a near-certainty: the "is it finished?" check runs every minute for as long as the session keeps naming the sibling drop, which here was over 10 hours.

Likely the same as #14: a brand-new daily campaign (OWCS MSC Day 2) doesn't even need the glitch. It isn't in dropCampaignsInProgress yet, so the first poll that reports Day 1's finished drop marks Day 2 completed straight away.

Side effect: every false "complete" also calls ProcessDrops. While this lasts, the farmer does roughly two full dashboard, inventory and details fetches per minute instead of one every 15 minutes.

Fix

Fixed in #19: the completion check only runs when the session's drop belongs to the picked campaign, and inventory fetch errors are returned instead of discarded.

Verified live on the affected account. After removing the campaign from completed_campaigns, Twitch kept returning the WARDOG drop for the next 4 hours; the patched check skipped it 238 times with no false completion. The remaining three drops (660, 780 and 900 minutes) were claimed, and the campaign was then marked completed the normal way once Twitch removed it from the in-progress list, with all 9 rewards owned.

Recommendation (design decision, not part of the PR)

Once a campaign is marked completed, the only way to undo it is editing config.json:

  • Config.UnmarkCampaignCompleted (internal/config/config.go:519) has had no caller since 1e74d02 removed scrubStaleCompleted.
  • That commit message says a stuck campaign can be re-enabled with the TUI's t key, but re-enabling (SetCampaignEnabled) only edits disabled_campaigns.
  • The web UI renders the toggle on COMPLETED rows as disabled-ui and ignores clicks on it (internal/web/static/index.html:1262, :1310).

Two options, your call:

  • Manual: a "Re-open" action on COMPLETED rows (web UI and TUI) that calls UnmarkCampaignCompleted and re-runs ProcessDrops. It doesn't need a confirmation dialog: a campaign that really is finished has nothing left to earn, so the selector skips it and the next claim pass marks it completed again. The row already has the data to flag suspicious cases (COMPLETED but claimed_drops < watchable_drops).
  • Automatic: un-mark a completed campaign when Twitch still lists it in dropCampaignsInProgress with an earnable drop.

Workaround

Stop the farmer, remove the campaign ID from completed_campaigns in config.json, then start it again.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions