# 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`: ```rust 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 → `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`: ```rust 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 ```mermaid 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.