bountyPoller calls fetchBounty(id) with no fallback argument: wrong arity, and a backend outage silently blanks the bounty instead of surfacing an error

Open
#828 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
65/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active

Research direction

Read src/hooks/bountyPoller.ts alongside the fetchBounty declaration in src/lib/api.ts and the caller in src/hooks/useClaimRace.ts. First run npx tsc --noEmit to confirm the arity error, then trace how a failed poll updates bounty and error state. Done when the poller typechecks, a failed refresh preserves the last known bounty and exposes an error, and the successful polling path still updates the status.

Written by the indexing model from the issue text.

Description

bug

Location

src/hooks/bountyPoller.ts:45 inside startPolling's tick, against fetchBounty as declared in src/lib/api.ts (export const fetchBounty = cache(async (id: string, fallback: Bounty | undefined) => ...)).

Problem

Two related defects at the same call site.

1. Wrong arity. fetchBounty takes two required parameters. The poller calls it with one:

const result = await fetchBounty(id);

fallback is typed Bounty | undefined but is not optional (no ?), so this is a TS2554: Expected 2 arguments, but got 1 error. The sibling caller gets it right (src/hooks/useClaimRace.ts:77 passes fetchBounty(bountyId, undefined)), which is why the mistake is isolated to this file.

2. The error branch is unreachable, and failures look like "bounty does not exist". fetchBounty never throws — it is a fallback-style fetcher that swallows everything:

try { ... return { data: adaptBounty(raw), source: "live" }; }
catch { return { data: fallback, source: "mock" }; }

With fallback undefined, a backend error/timeout resolves to { data: undefined, source: "mock" }, and the poller then does:

entry.bounty = result.data ?? null;   // -> null
entry.source = result.source;
entry.error = null;                   // explicitly cleared

So entry.error can never be set by the normal path (the catch (err) block at :54-56 is dead code for the same reason #240 was dead code), and a transient outage overwrites an already-loaded bounty with null. useBountyStatus then returns bounty: null, error: null, isLoading: false — indistinguishable from "this bounty id does not exist" — and any consumer rendering bounty ? ... : <NotFound/> flips a populated status card to an empty/not-found state on a single failed poll, then back again on the next successful one.

Why it matters

  • The project does not typecheck (see the arity error), on top of whatever other build problems exist.
  • During a backend blip the bounty page's status area blanks out mid-session rather than keeping the last known value or showing "couldn't refresh". For a user mid-claim, a bounty that appears to vanish and reappear is both alarming and actionable in the wrong direction.
  • Because error is permanently null, every error-handling branch downstream of useBountyStatus().error is unreachable, so nobody can see that polling is failing at all.

Reproduction

  1. Open /issues/<id> against a running backend and let the status populate.
  2. Stop the backend (or block /bounties/:id at the network level).
  3. Within 5s the poller ticks, fetchBounty falls back, and entry.bounty becomes null while entry.error stays null.
  4. The status area renders as if the bounty does not exist; no error state, no retry message.

For the arity error: npx tsc --noEmit.

Suggested fix

Have the poller use a fetcher that reports failure instead of masking it. Either call the raw request<RawBounty> helper (exported for this kind of use) and adapt, or add a throwing variant to src/lib/api.ts and call it here. Then:

const raw = await fetchBountyOrThrow(id);   // throws on network/HTTP/parse failure
entry.bounty = raw;
entry.error = null;

and in the catch, set entry.error without clobbering entry.bounty — keep the last known good value so the UI can show "last updated Xs ago, retrying" rather than an empty card. If the fallback-style fetcher is kept for now, at minimum pass the second argument explicitly and treat source === "mock" as the failure signal (entry.error = new Error(...), bounty left untouched) instead of writing null.

Dominant language
TypeScript
Stars
1
Forks
94
Avg merge
1h 2m
Merged PRs (30d)
25

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from MergeFi/frontend

All issues in MergeFi/frontend

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.