GitHub Read Relay
The relay is the core of Octopool: a single Worker endpoint that performs read-only GitHub REST requests on behalf of a caller, using a pooled GitHub identity selected by the pool coordinator.
Source: src/index.ts (relayGitHub), src/policy.ts, src/github.ts.
#POST /v1/github/request
Authenticated with a caller bearer token scoped to the target pool (see Auth).
Request body:
{
"pool": "maintainers",
"method": "GET",
"path": "/repos/openclaw/openclaw/pulls/123",
"query": { "per_page": "100" },
"headers": { "accept": "application/vnd.github+json" },
"route_hint": { "owner": "openclaw", "repo": "openclaw", "kind": "pr_view" },
"cache_key": "...",
"idempotency_key": "..."
}
pool,method,pathare required, non-empty strings.- Only
GETis enabled. Any other method is rejected with403 method_denied. queryvalues are strings or string arrays. Keys are rejected if they look secret-bearing (token,secret,password,api_key, …).headersare filtered down toaccept,x-github-api-version,if-none-match,if-modified-since. Everything else is dropped.route_hint,cache_key,idempotency_keyare accepted and currently advisory.
#Path validation
path must be an absolute GitHub API path. It is rejected (400 invalid_path) if it contains ://, \, ?, #, .., a bare dot segment, or percent-encoded path traversal (%2e, %5c). The relay only ever talks to api.github.com.
#Response envelope
{
"status": 200,
"headers": {
"content-type": "application/json",
"etag": "...",
"x-ratelimit-remaining": "4998",
"x-ratelimit-reset": "1780000000"
},
"body": {},
"body_encoding": "json",
"identity": { "id": "ghapp_openclaw_openclaw", "kind": "github_app" },
"relay": {
"pool": "maintainers",
"request_id": "...",
"cacheable": true,
"cache": "miss",
"stale_ok": false,
"route_kind": "pr_view",
"lease_reason": "highest_remaining"
}
}
headersare filtered to a safe allowlist (content negotiation, caching, rate-limit, request id). Authorization and cookies never leave the Worker.body_encodingisjson,text, orbase64. Binary responses are base64-encoded.cacheishit,miss, orbypass(route not cacheable).lease_reasonissticky,highest_remaining, orfallback— see Identities & routing.
#Supported routes
Routes are classified in src/policy.ts. Only the following read-only shapes are enabled; anything else returns 403 route_denied:
pr_view,pr_files,pr_review_comments,pr_reviewscommit_check_runs,commit_statusrun_list,run_view,run_jobs,run_artifactsjob_view,job_logs,check_run_annotationsissue_view,issue_comments,issue_timelinebranch_viewrate_limit
job_logs is a large-payload, log-class route: it follows GitHub's signed redirect to *.actions.githubusercontent.com / *.blob.core.windows.net, is not cached, and is gated by the pool's allow_logs policy.
#Policy gates
classifyRoute enforces, per pool:
allowed_owners— the route owner must be allowlisted, else403 owner_denied. Defaults toDEFAULT_ALLOWED_OWNERS(openclaw).allow_logs— log routes require it (defaulttrue), else403 logs_denied.allow_search— search routes require it (defaultfalse), else403 search_denied.
Every repo route additionally passes a public-visibility check before a pooled identity or cache entry is used — see Cache & public-repo guard.
#Safety limits
- Redirects from
api.github.comare denied (502 github_redirect_denied) except the log-download flow above. - Response bodies are capped: 1 MiB default, up to
MAX_RESPONSE_BYTES(2 MiB) for large-payload routes. Over-cap responses fail with502 github_response_too_large. - Requests time out after
REQUEST_TIMEOUT_MS(15s default).
#Audit
Every routed request — success or failure — writes an audit_events row with request id, caller, pool, route key, route kind, identity id, status, error code, and duration. Audit writes happen via ctx.waitUntil and never block the response.