Skip to content

GitHub API fallback eligibility may not be derived from the issue-comment watch store #226

Description

@khaliqgant

Follow-up from #225. Filed so this does not live only in chat. Not dispatched deliberately — no readiness label, no title prefix.

Summary

#deriveGithubApiFallbackEligibility resolves whether an issue may be re-read through the direct GitHub API fallback by scanning durable decision sources. As of 91add5f it covers three:

  • the in-flight batch (batch.inFlight)
  • waiting clarifications (listWaitingClarifications)
  • non-terminal dispatch lifecycles (listDispatchLifecycles)

There appears to be a fourth durable store holding a TriageDecision that it does not consult.

Evidence

  • escalationWatchRecord (src/orchestrator/factory.ts:15796) constructs an InFlightIssue locally from a bare TriageDecision, rather than one restored into the batch — the same shape as the clarification path that caused the round-6 defect in fix(factory): fall back to direct GitHub API when the Relayfile issue projection is stale #225.
  • It is called at src/orchestrator/factory.ts:8709 with pending.decision, feeding #handleTriageEscalationGithubAnswer, which reads the issue via #readIssue with no decisionHint.
  • That decision originates from GithubIssueCommentWatchPending.decision, durable via #state.setGithubIssueCommentWatch / getGithubIssueCommentWatch.

This is a distinct lifecycle stage from the three covered sources: a triage-time escalation, resolved by a GitHub comment reply before the issue is ever dispatched.

Status — structurally confirmed, not demonstrated

The code path is confirmed by inspection. What is not established is whether a pre-dispatch escalation decision can actually carry issueResolution.source === 'github-api-fallback'. It is plausible — a CLI-targeted triage of an issue whose projection is stale — but no live failure has been reproduced, and this should not be repeated as a confirmed defect until someone does.

If it can occur, the failure shape matches the one fixed in #225 round 6: after a restart the eligibility cache is empty, derivation misses the decision, and the read fails on an issue that is actually fine.

Why this is filed rather than patched

Adding a fourth source at this call site would repeat the method that produced the problem. Rounds 4 and 6 of #225 were the same root failure — the enumeration of "where can a decision live" was built reactively around whichever case a reviewer named, instead of surveyed up front.

The right fix is a complete audit of every local InFlightIssue / decision: construction site in src/orchestrator/factory.ts, producing the enumeration mechanically before any code changes, so the set is proven rather than asserted. This one was found by grepping for that shape, not by a review round.

Suggested acceptance criteria

  • A mechanically-produced list of every construction site that wraps a TriageDecision outside the in-flight batch, in the issue or the PR description.
  • Eligibility derivation covers every durable source on that list.
  • A guard test per source: a decision reachable from only that source resolves eligible.
  • A determination, either way, on whether a pre-dispatch escalation decision can carry github-api-fallback — with the reasoning recorded even if the answer is "it cannot".

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