GitHub Traffic Control

Central gateway plan and current implementation for rate-limit-aware GitHub API traffic from Issue Reporter, Hub approvals, and Agent Orchestrator.

This page describes how Clapilot routes GitHub REST API traffic and how rate limits are handled. It is for developers touching Issue Reporter, Hub issue approval, or Agent Orchestrator GitHub calls. Part of this design is shipped (the shared githubTrafficFetch helper and the write queue table); the gateway budgets, queue drain worker, and shared webhook ingestion are still planned and are marked as such below.

Architecture Decision

Clapilot will route internal GitHub REST API calls through a central GitHub Traffic Control layer. The first implementation scope is intentionally narrow:

  • normalize GitHub rate-limit detection for Issue Reporter and Hub approval writes
  • log GitHub rate-limit headers from the shared web helper
  • queue Hub approval issue-creation writes when GitHub returns a primary or secondary rate limit
  • document the current GitHub call inventory and rollout path for Agent Orchestrator traffic

The long-term gateway owns request budgeting, read caching, write queues, and webhook ingestion. Existing Agent Orchestrator code has a local githubApiFetch wrapper that already implements the read side of this design for the poll loop: ETag conditional requests with a bounded LRU replay cache, per-token x-ratelimit-* tracking, budget-aware scan pacing, and rate-limit cooldowns (see bundled-modules/agent-orchestrator/api/github-api-cache.mjs and the Agent Orchestrator module page). When the shared gateway lands, that wrapper should be folded into it rather than kept as a second permanent abstraction.

Current Call Inventory

AreaCurrent pathTraffic typeNotes
Issue Reportersrc/lib/issue-reporter.tsPOST /repos/{owner}/{repo}/issuesNow uses the shared githubTrafficFetch helper for header logging and rate-limit errors.
Hub Approvalsrc/app/api/hub/issues/[id]/route.ts -> createGithubIssueFromHubReportuser-triggered writeRate-limited writes are inserted into github_traffic_control_queue and surfaced as queued instead of generic 502.
Agent Orchestrator repo listbundled-modules/agent-orchestrator/api/handler.mjsuser-visible readUses local repo-list cache keyed by token hash.
PR Review automationbundled-modules/agent-orchestrator/api/handler.mjsbackground reads and review writesUses local githubApiFetch (ETag conditional GETs, budget pacing); should migrate to shared gateway in the next implementation slice.
Issue Observerbundled-modules/agent-orchestrator/api/handler.mjsbackground reads, comments, issue state writesPer-tick poll caches for default branch, open issues, and open PRs, plus cross-tick ETag replay on 304.
Mention Observerbundled-modules/agent-orchestrator/api/handler.mjs and github-mention-observer.mjsbackground reads, reaction/comment writesCandidate detection polls issues, PRs, comments, reviews, and check data; only threads updated since the previous scan are expanded.
Tracked PR follow-upbundled-modules/agent-orchestrator/api/handler.mjs and tracked-pr-followup.mjsbackground readsReads comments, reviews, check-runs, and PR state for follow-up decisions; unchanged resources come back as free 304s.
CI preview cleanupscripts/ci/cleanup-pr-preview.shCI-only readStays outside product gateway unless moved into long-running app automation.

Gateway Contract

Every product GitHub request should record:

  • method and normalized endpoint
  • actor, for example issue_reporter, hub_approval, agent_orchestrator_pr_review, or supervisor
  • token or installation identity without storing the secret
  • x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset, x-ratelimit-used, x-ratelimit-resource
  • x-github-request-id
  • response status and rate-limit classification

Rate-limit errors must map to a user-facing 429 or queued response, not a generic 502. The message should include the reset timestamp and GitHub request id when available.

Queue

github_traffic_control_queue stores write work that should not be dropped on rate limit. The first queued action is hub_issue_create; it records the Hub issue id and reviewer in the JSON payload with status waiting_for_rate_limit.

Queue states are:

  • pending
  • waiting_for_rate_limit
  • failed_permanent
  • completed

Status: enqueueing works today (enqueueGithubWrite in src/lib/github-traffic-control.ts), but the drain worker is not implemented yet. A follow-up worker should drain due rows by priority, call the same gateway, increment attempts, and mark permanent failures only after non-rate-limit errors or an attempt cap. Until that worker exists, queued rows stay in the table and need manual follow-up.

Request Budgets

Priority order:

  1. user-triggered writes
  2. active coding/debug jobs
  3. user-visible reads
  4. background sync and supervisor polling

When remaining budget is low, background polling should skip or extend its interval before user-triggered actions are affected. Agent Orchestrator implements this locally today: runSymphonyPollTick calls decideGithubScanForPollTick before touching GitHub, keeps a 15% reserve of the hourly limit (at least 100 requests) untouched by polling, stretches the GitHub scan interval up to 15 minutes when the remaining budget cannot sustain the configured poll cadence until the reset, and skips the GitHub section entirely while a token is in rate-limit cooldown. The shared gateway should take over this check with the same priorities.

Read Caching Strategy

Recommended cache windows:

  • repo metadata: 10 to 60 minutes
  • issue and PR metadata: 60 to 120 seconds
  • check status: 30 to 90 seconds
  • comments and reviews: 60 to 180 seconds
  • workflow logs: explicit request only, or fetched when a failed check requires diagnostics

In-flight request coalescing should key by token identity, method, endpoint, and query params so parallel agents share the same read result.

Shipped in Agent Orchestrator: every GitHub GET carries If-None-Match from a bounded LRU keyed by token hash and full URL; 304 Not Modified replays the cached body and is not charged against the primary limit, so the time windows above only matter for endpoints whose payload actually changed. Per-tick poll caches additionally coalesce the open-PR, open-issue, and default-branch reads across the PR review, issue observer, and mention observer features.

Webhook Follow-up

Today, GitHub webhooks are received only by the Agent Orchestrator module (public module endpoint /api/modules/agent-orchestrator/webhook); there is no shared product-level webhook route yet. The planned shared gateway should add a GitHub webhook endpoint for:

  • issues
  • issue_comment
  • pull_request
  • pull_request_review
  • check_run
  • check_suite
  • workflow_run

Webhook events should invalidate gateway caches and enqueue agent work. Polling remains a fallback and supervisor recovery path, but it should not be the primary freshness mechanism for active repositories. Agent Orchestrator now also uses completed failing workflow_run, check_run, and check_suite webhook payloads to start a configured default-branch CI fix run that opens a PR, while PR-branch failures continue through tracked PR follow-up.