Files

10 KiB

Technical Spec: GitHub PR Prompt Chip Retryable Network Failures

Problem

The GitHub PR prompt chip currently uses the generic prompt-chip suppress_on_failure runtime path. That path caches any failed shell command by fingerprint in last_failure_fingerprint and skips future executions until the fingerprint changes. For the GitHub PR chip, this is too broad. A no-network failure, DNS failure, GitHub outage, API/rate-limit failure, timeout, or other transient gh pr view failure can resolve without any change to the session, directory, branch, executable set, or invalidating command count. These failures should not hide the chip or block a later retry. Deterministic setup failures such as missing gh or unauthenticated gh should continue to suppress the default PR chip behavior introduced by APP-3908.

Relevant code

  • specs/APP-3908/PRODUCT.md — existing product behavior for default PR chip inclusion and deterministic readiness suppression.
  • specs/APP-3908/TECH.md — original technical plan for PR chip default validation.
  • app/src/context_chips/context_chip.rs (207-309)ChipRuntimePolicy, suppress_on_failure, runtime availability, and disabled reasons.
  • app/src/context_chips/mod.rs (236-263)ContextChipKind::GithubPullRequest runtime policy: required gh/git, local-only execution, 5s timeout, suppress_on_failure, and git/gh/gt invalidation.
  • app/src/context_chips/current_prompt.rs (343-520) — fingerprint construction and maybe_skip_fetch_due_to_matching_fingerprint.
  • app/src/context_chips/current_prompt.rs (620-839) — shell chip execution, timeout handling, PR validation transitions, and last_failure_fingerprint updates.
  • app/src/context_chips/current_prompt.rs (1345-1518)is_gh_auth_error, default PR chip suppression, and validation state updates.
  • app/src/context_chips/scripts/github_pull_request_prompt_chip.sh — shell script that maps benign repo/no-PR states to successful empty output and forwards non-benign gh pr view failures to stderr.
  • app/src/context_chips/current_prompt_test.rs (631-843) — current PR chip suppression test that expects command failures to cache the failure fingerprint.
  • app/src/context_chips/current_prompt_test.rs (1033-1184)RecordingCommandExecutor test helper for shell command outputs.
  • app/src/terminal/session_settings.rs (96-113, 338-349)GithubPrPromptChipDefaultValidation and local-only persisted validation setting.

Current state

ContextChipKind::GithubPullRequest is configured with ChipRuntimePolicy::with_suppress_on_failure(). In CurrentPrompt::fetch_chip_value_once, two paths cache failures:

  • If the shell command times out, state.last_failure_fingerprint = current_fingerprint.
  • If the command exits unsuccessfully and suppress_on_failure is true, state.last_failure_fingerprint = current_fingerprint. Before executing a chip, the runtime checks whether last_failure_fingerprint matches the current fingerprint. If it does, the chip value is cleared, status becomes Cached, and no command is run. The PR chip already has extra validation logic: missing gh from ChipAvailability::Disabled(RequiresExecutable { command: "gh" }) suppresses default inclusion, is_gh_auth_error(stderr) suppresses default inclusion after an auth failure, and successful command execution validates the default. The gap is that validation state and per-fingerprint failure caching are not separated. A transient network error does not set GithubPrPromptChipDefaultValidation::Suppressed, but it still populates last_failure_fingerprint, which suppresses the chip for the same fingerprint and prevents the normal runtime retry.

Proposed changes

1. Introduce a PR chip command outcome classifier

Add a small classifier near the PR validation helpers in current_prompt.rs:

enum GithubPrPromptChipCommandOutcome {
    Validated,
    DeterministicAuthFailure,
    RetryableFailure,
}

The classifier should take the command output and timeout flag from the shell execution completion path. It should only be used for ContextChipKind::GithubPullRequest. Classification:

  • CommandExitStatus::SuccessValidated
  • stderr matching the existing narrow auth patterns in is_gh_auth_errorDeterministicAuthFailure
  • timeout → RetryableFailure
  • all other command failures → RetryableFailure Do not attempt to maintain a broad list of every possible network error string. Treat unknown gh command failures as retryable unless they are a known deterministic setup failure.

2. Gate PR chip failure fingerprint updates by outcome

Keep the generic suppress_on_failure behavior unchanged for other chips. For GithubPullRequest, replace the unconditional failed-command cache update with outcome-specific behavior:

  • Validated: call maybe_validate_github_pr_default(ctx), clear last_failure_fingerprint if it matches the current fingerprint, and store the command output as today.
  • DeterministicAuthFailure: call maybe_suppress_github_pr_default(ctx) and set last_failure_fingerprint = current_fingerprint.
  • RetryableFailure: do not call maybe_suppress_github_pr_default(ctx) and do not set last_failure_fingerprint. If the current fingerprint is already cached from a previous retryable failure after partial execution, clear it. Timeout handling should follow the same PR-specific branch. Timeouts for non-PR chips can keep the existing generic behavior.

3. Keep missing executable suppression unchanged

The availability path should continue to suppress the default PR chip when gh is missing:

  • ChipAvailability::Disabled(ChipDisabledReason::RequiresExecutable { command: "gh" })maybe_suppress_github_pr_default(ctx) This is not a transient command failure because the command cannot run until local setup changes. The existing maybe_unsuppress_github_pr_default path can continue to reset suppression when gh appears on $PATH.

4. Preserve benign empty states

Do not change github_pull_request_prompt_chip.sh for this work. Its current behavior is desirable:

  • not in a git repo, detached HEAD, missing origin, non-GitHub remote, and no open PR exit successfully with empty output
  • successful empty output does not render a PR chip value
  • successful empty output can still validate that the command path is healthy when gh pr view was reached

5. Factor the failure-cache decision for testability

To avoid embedding special cases throughout fetch_chip_value_once, add a helper that determines whether a failure should set last_failure_fingerprint:

fn should_cache_failure_fingerprint(
    chip_kind: &ContextChipKind,
    output: Option<&CommandOutput>,
    timed_out: bool,
) -> bool

Expected behavior:

  • non-PR chips return the existing generic suppress_on_failure && failed_or_timed_out result
  • PR chip auth failures return true
  • PR chip retryable failures and timeouts return false An equivalent helper returning a richer enum is also acceptable if it keeps validation and cache decisions together.

End-to-end flow

flowchart TD
    A[GitHub PR chip scheduled] --> B{Availability}
    B -->|Missing gh| C[Set validation Suppressed]
    B -->|Enabled| D[Run github_pull_request_prompt_chip.sh]
    D --> E{Command result}
    E -->|Success URL or benign empty| F[Validate default and render/cache value as applicable]
    E -->|Auth failure| G[Set validation Suppressed and cache failure fingerprint]
    E -->|No network, timeout, rate limit, GitHub outage, unknown failure| H[Clear chip value without caching failure fingerprint]
    H --> I[Next runtime refresh or invalidation can run command again]

Risks and mitigations

  • Retrying too aggressively: The PR chip already has a 30s periodic refresh and command-based invalidation. Avoid adding a new retry loop; simply avoid poisoning the existing cache on retryable failures.
  • False positives for auth failures: Keep is_gh_auth_error narrow. Unknown failures should be retryable rather than suppressing the chip.
  • Behavior change for the existing suppression test: Update the current test to distinguish auth failures from retryable failures instead of asserting all PR command failures cache a fingerprint.
  • Repeated failures while offline: The chip may retry periodically while offline. This is acceptable because the command has a 5s timeout and this behavior is limited to the PR chip. If needed later, add bounded backoff rather than suppression.
  • Default validation state remains suppressed from previous deterministic failures: This spec only changes transient command failure behavior. It does not require clearing already-suppressed validation state except through existing setup-change logic.

Testing and validation

  • Unit test PR chip auth failure:
    • command stderr contains an auth error
    • github_pr_chip_default_validation becomes Suppressed
    • last_failure_fingerprint is set
    • revisiting the same fingerprint skips execution
  • Unit test PR chip network failure:
    • command stderr is a representative network failure, such as Post "https://api.github.com/graphql": dial tcp: lookup api.github.com: no such host
    • validation state does not become Suppressed
    • last_failure_fingerprint remains None
    • a subsequent fetch with the same fingerprint runs the command again and can succeed
  • Unit test PR chip timeout:
    • timeout does not suppress validation
    • timeout does not set last_failure_fingerprint
  • Unit test generic suppress-on-failure behavior for another shell chip remains unchanged, or keep existing coverage if sufficient.
  • Manual validation:
    • disconnect network in a GitHub repo with an authenticated gh
    • trigger the PR chip and confirm the chip disappears or stays empty without suppressing the default
    • restore network and confirm the chip can re-run and show the PR without changing branches, directories, prompt settings, or restarting Warp

Follow-ups

  • Consider bounded retry backoff for repeated retryable PR chip failures if periodic command execution is too noisy while offline.
  • Consider surfacing a lightweight transient error state in debug logs or prompt-chip logs if this is hard to diagnose manually.