omni-dev 0.46.0

AI-powered git commit rewriter, PR generator, and MCP server for Jira, Confluence, Datadog, Gmail, and Drive.
Documentation
# GitHub conditional request evaluation

Evaluated for [#1814](https://github.com/rust-works/omni-dev/issues/1814) on
2026-10-03. **Decision: retain the current GraphQL data-fetch paths.** No current
consumer qualifies for a REST conditional-request implementation. Resolve the
evaluation as not applicable to current consumers and revisit it when a concrete
single-resource poller meets the criteria below.

This is an evaluation of cache validation, separate from GitHub App authentication
in [#1813](https://github.com/rust-works/omni-dev/issues/1813). It adds no client,
cache schema, dependency, or runtime behavior.

## What conditional requests buy

REST validators apply to the requested representation. An authenticated conditional
GET returning `304 Not Modified` avoids the primary REST charge; a changed response
still costs a request. Neither result eliminates HTTP latency, subprocess overhead,
or secondary-limit considerations. A validator for an issue does not establish
freshness of separately fetched comments, checks, reviews, or Projects-v2 items.
See [GitHub's REST best practices](https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api#use-conditional-requests).

The existing `gh api graphql` queries are POST requests with query bodies and have
no REST conditional-GET validation path. Adding `If-None-Match` to the shared
GraphQL runner would not provide that mechanism. REST and GraphQL have separate
primary budgets, so reducing REST charges alone is not evidence of lower total
cost; compare round trips, response coverage, and both budgets.
See [GitHub's rate-limit documentation](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api).

## Current consumers and candidate outcomes

- **Issue documents and comments:** [`github_issues.rs`]../../src/github_issues.rs
  uses repository-grouped aliases in batches of at most 25 references. Full issue
  documents include the latest 100 comments and closing-PR references. A REST
  issue GET alone does not supply that aggregate; comment and other representations
  need their own requests and validators. Retain the bounded GraphQL batches.
- **Labels and board data:** the audited issue-document and PR queries do not
  fetch label collections or Projects-v2 board items. These are motivation for a
  future consumer, not current cached representations to convert. Evaluate such
  a consumer's exact endpoint and dependencies when it exists; neither the current
  disk cache nor an issue GET's validator establishes board freshness.
- **Cached issue or PR state:** [`IssueCache`]../../src/github_issues/cache.rs
  reuses documents for a default 300 seconds. `fetch_issues_cached_current` and
  `fetch_items_cached_current` validate state with a shallow aliased `fetch_states`
  request, or use the caller's known listing state. State mismatches and failed
  checks cause full fetches. Replacing this with per-item REST requests adds a
  second representation and fetch path for an already batched operation.
- **Timestamp-only freshness:** `fetch_updated_at` in `github_issues.rs`, added for
  [#1815]https://github.com/rust-works/omni-dev/issues/1815, already offers bounded,
  aliased issue/PR timestamp reads. It is a helper, not wired into the disk cache:
  callers must retain a baseline and choose when to fetch full data. A failed or
  missing check cannot certify freshness, and it is not an atomic snapshot with
  the full fetch. Do not assume a timestamp detects every related-resource edit;
  Projects-v2 board changes require their own freshness strategy.
- **PR check badges:** [`pr_status.rs`]../../src/pr_status.rs resolves branch-to-PR
  discovery and commit check/status rollups together across repositories. Its
  module documents measured GraphQL costs of 1 point up to roughly 50 branches,
  rising for larger batches. REST would need discovery plus the appropriate
  commit statuses/checks; a PR GET's validator cannot certify the badge. Retain
  GraphQL and the daemon's existing coalescing, adaptive polling, and budget backoff.
- **Merge eligibility:** `resolve_merge_targets` in `pr_status.rs` fetches PR
  identity, head OID, draft/merge state, and checks freshly at enqueue time. This
  safety decision needs current aggregate facts, not a cached PR representation
  whose validator covers only one part. Retain the fresh batched query.
- **Open PR lists and PR creation context:** the daemon's
  [`OpenPrCache`]../../src/daemon/services/worktrees.rs shares `gh pr list` results
  with a default 60-second TTL; the
  [extension fallback]../../editors/vscode/src/github.ts uses the same CLI when
  needed. [`create_pr.rs`]../../src/cli/git/create_pr.rs consumes PR context and
  invokes mutations, which are not conditional-read candidates. A REST pull-list
  endpoint is plausible for list-only polling, but replacing today's paths would
  require matching fields, filters, pagination, and cache behavior. There is no
  demonstrated bottleneck justifying that migration. GitHub CLI implements both
  its [ordinary PR list]https://github.com/cli/cli/blob/trunk/pkg/cmd/pr/shared/lister.go
  and [search list]https://github.com/cli/cli/blob/trunk/pkg/cmd/pr/list/http.go
  using GraphQL.
- **Rate-limit monitoring, the REST exception:**
  [`github_rate_limit::run_gh_rate_limit`]../../src/github_rate_limit.rs already
  calls `gh api rate_limit`. Unlike the issue's initial all-GraphQL inventory,
  this is REST. [GET /rate_limit]https://docs.github.com/en/rest/rate-limit/rate-limit#get-rate-limit-status-for-the-authenticated-user
  is already exempt from the primary REST limit. Adding validators cannot improve
  that budget, and the consumer needs changing counters across resource families.
  Retain the existing periodic overview and GraphQL budget observations.

Aliasing does not make GraphQL unlimited: connection selection, node limits, and
query size still matter. Conversely, unchanged REST polls can cost zero primary
points, so GraphQL is not universally cheaper. For current consumers, preserving
batching and aggregate coverage outweighs that possible saving. This is a source
audit and architectural judgment, not a measured REST-versus-GraphQL benchmark.

## Transport choice for a future qualified consumer

Prefer `gh api <rest-path> --method GET --include` through
[`github_metrics::run_gh`](../../src/github_metrics.rs), following the repository's
existing shell-`gh` approach in [ADR-0003](../adrs/adr-0003.md). The CLI already
handles the active account and host; the wrapper preserves counting and request
logging. `--include` exposes the status line and headers, and `--header` can send
an opaque ETag or Last-Modified value. These options are documented in the
[GitHub CLI manual](https://cli.github.com/manual/gh_api).

A direct `reqwest` client makes typed status/header handling easier and could
reuse connections, but it would need a credential acquisition and host policy,
plus equivalent instrumentation and redaction. No current consumer warrants
that additional ownership. Header parsing alone is not a reason to migrate.

A future implementation must:

1. Key the cached body and validator by authenticated account/host, exact URL
   including filters and page, and representation headers/API version. Reset or
   miss on identity changes. Store opaque validators unchanged and retain a body
   for the same representation; reuse the disk cache's private-data protections.
2. Distinguish status/headers from the JSON body. On `200`, validate and replace
   body plus validator together. On `304`, reuse only the matching valid body;
   without one, retry unconditionally. Never parse the empty 304 body as JSON.
   Handle redirects and verify the supported `gh` version's exit behavior rather
   than relying solely on subprocess success.
3. Treat `401`, `403`, `404`, `429`, malformed responses, and transport failures
   as errors or explicit misses under the consumer's policy, never proof of
   freshness. Respect rate-limit headers and backoff. In particular, `404` may
   mean lost access to a private resource.
4. Use ETag when supplied, or Last-Modified when supported; without a validator,
   perform an ordinary fetch. Validate each paginated representation separately
   and account for collection membership changes. Keep existing TTL, explicit
   refresh, and mutation-triggered invalidation semantics deliberate.
5. Test unchanged, changed, missing-body, missing-validator, identity-switch,
   pagination, and failure cases using fake `gh` responses before enabling it.

## When to revisit

Reopen the design for a recurring single-resource REST read whose representation
fully covers the consumer's freshness requirement, with stable request parameters,
a high unchanged-response rate, and no useful aliased-batch alternative. Measure
unchanged and changed workloads, including total HTTP calls, subprocess time,
payload size, primary charges, and secondary-limit behavior. A successful result
must justify both the migration and its cache/error-handling complexity.

Until then, use existing TTL/state checks and the timestamp helper where its
documented coverage fits. Board freshness, webhook infrastructure, and new
authentication remain separate work.