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::GithubPullRequestruntime policy: requiredgh/git, local-only execution, 5s timeout,suppress_on_failure, andgit/gh/gtinvalidation.app/src/context_chips/current_prompt.rs (343-520)— fingerprint construction andmaybe_skip_fetch_due_to_matching_fingerprint.app/src/context_chips/current_prompt.rs (620-839)— shell chip execution, timeout handling, PR validation transitions, andlast_failure_fingerprintupdates.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-benigngh pr viewfailures 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)—RecordingCommandExecutortest helper for shell command outputs.app/src/terminal/session_settings.rs (96-113, 338-349)—GithubPrPromptChipDefaultValidationand 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_failureis true,state.last_failure_fingerprint = current_fingerprint. Before executing a chip, the runtime checks whetherlast_failure_fingerprintmatches the current fingerprint. If it does, the chip value is cleared, status becomesCached, and no command is run. The PR chip already has extra validation logic: missingghfromChipAvailability::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 setGithubPrPromptChipDefaultValidation::Suppressed, but it still populateslast_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::Success→Validated- stderr matching the existing narrow auth patterns in
is_gh_auth_error→DeterministicAuthFailure - timeout →
RetryableFailure - all other command failures →
RetryableFailureDo not attempt to maintain a broad list of every possible network error string. Treat unknownghcommand 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: callmaybe_validate_github_pr_default(ctx), clearlast_failure_fingerprintif it matches the current fingerprint, and store the command output as today.DeterministicAuthFailure: callmaybe_suppress_github_pr_default(ctx)and setlast_failure_fingerprint = current_fingerprint.RetryableFailure: do not callmaybe_suppress_github_pr_default(ctx)and do not setlast_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 existingmaybe_unsuppress_github_pr_defaultpath can continue to reset suppression whenghappears 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 viewwas 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_outresult - 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_errornarrow. 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_validationbecomesSuppressedlast_failure_fingerprintis 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_fingerprintremainsNone- a subsequent fetch with the same fingerprint runs the command again and can succeed
- command stderr is a representative network failure, such as
- 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
- disconnect network in a GitHub repo with an authenticated
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.