Skip to content

github capture: hasSavedWork counts failure residue as backlog and misses budget-boundary rotations, mis-timing the retry cadence after a failed enumeration #1305

Description

@philcunliffe

Source: PR #1302, triaged at head cdf51d2a5517e3214cf2e60cd8a41bcc95a742c4 (review round 2 residual finding 4).
Severity: MEDIUM (accepted, not fixed in the PR).

Location

  • hypaware-core/plugins-workspace/github/src/capture.js:745 (hasSavedWork), used at capture.js:114 on the failed-enumeration early return

Observed evidence and behavior

When all_visible enumeration fails, the tick reports pending: hasSavedWork(cursors), which counts any repository cursor holding work. That set is approximate in both directions:

  • Over-report: a repository that keeps failing leaves work behind, and the per-repo catch (capture.js:162) deliberately refuses to call that backlog (LLP 0360#cadence). While enumeration is also failing, nextCaptureDelay (source.js:148) clamps a 24h source to the 15-minute backlog cadence for as long as both faults persist: one extra failing listViewerRepos call per 15 minutes against an already-broken endpoint.
  • Under-report: a rotation that exhausted its budget exactly at a repository boundary marks pending only via visited < repos.length (capture.js:182), which no cursor records. A following failed enumeration then reads false and the saved rotation resumes a full poll interval (up to 24h) later instead of in 15 minutes.

The PR corrected the JSDoc claim (the doc now states the approximation and both directions) instead of the behavior.

Why deferral is safe

Both residual errors are bounded and non-destructive: the worst case is a mis-timed retry or one delayed resume, never lost rows or a corrupted cursor. Both self-heal the moment enumeration succeeds, because the success path recomputes pending from the real signal. The pathological combinations also require enumeration to be persistently broken, which hyp status surfaces via lastError. An exact answer is not on disk today: distinguishing budget residue from failure residue needs a new cursor field, and the alternative (pending: undefined meaning "this tick learned nothing", so source.js keeps its previous flag, matching what the old throw path did) ripples through the shared return type in tick.js, commands.js, and source.js.

Acceptance condition

A failed enumeration leaves the source's backlog cadence exactly as accurate as the durable state allows in both directions: failure residue alone does not pin a daily source to the 15-minute cadence, and a budget-exhausted rotation interrupted by a failed enumeration still resumes on the backlog cadence. Tests cover both: (a) cursors holding only failure-residue work plus a failed enumeration do not yield the backlog cadence, and (b) an exhausted-at-boundary rotation followed by a failed enumeration does. (A pending: undefined "learned nothing" contract or an explicit durable pending marker both satisfy this.)

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

    neutral:fixDelegate this issue to neutral for an autonomous fix attempt (reproduce -> fix -> PR)neutral:stuckneutral attempted this but cannot complete it autonomously — needs a human

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions