Skip to content

[BUG] Referencing files in a private GitHub repository burns through anonymous GitHub API quota and hits the rate limit #3185

Open

Description

@halby24

[BUG] Referencing files in a private GitHub repository burns through anonymous GitHub API quota and hits the rate limit

Problem

When running apm install with a private repository as a dependency, every Markdown reference (skills, agents, etc.) triggers anonymous requests to the GitHub API. These consume the anonymous quota, so the budget is quickly exhausted when there are many files or when apm install is run multiple times in a short period.

Once the anonymous quota is exhausted, the API starts returning 403 instead of 404, and apm no longer retries with a token.

Network traffic during apm install

Per dependency

  • Existence check via GET /repos/<owner>/<repo> (install/validation.py:685-719)
  • GET /repos/<owner>/<repo>/commits/main: anonymous 404 → token 200. Costs 1 anonymous request per dep

Per file

# Request Authorization Observed result Anonymous quota cost
1 GET https://github.com/raw/<owner>/<repo>/main/some-skill.md none 404 none (CDN, outside quota)
2 GET https://github.com/raw/<owner>/<repo>/master/some-skill.md none 404 none
3 GET https://github.com/proxy/api.github.com/repos/<owner>/<repo>/contents/some-skill.md?ref=main none 404 1
4 Same with ?ref=master none 404 1
5 Same with ?ref=main with token 200 (fetch succeeds) authenticated quota (5000/h bucket)

Corresponding code: deps/download_strategies.py:1184-1260 (_download_public_github_file_anonymous_first).

Reproduction

Run the following against a manifest that references a private repository:

  1. Exhaust the anonymous REST quota (60/h)
  2. Confirm "remaining": 0 via curl https://github.com/proxy/api.github.com/rate_limit
  3. Run apm install or apm update → EXIT=1 with one identical error per file

Example error

Below is an example of the errors I got against my own private repository (one per file):

[x] 7 packages failed:
  +- agent-kit-builder -- Failed to resolve transitive dep halby24/agent-kit (via agent-kit-common > agent-kit-builder): GitHub API
throttle for github.com (HTTP 403); sparse Git transport failed: git file fetch failed: /usr/bin/git fetch --filter=blob:none: remote:
Invalid username or token. Password *** is not supported for Git operations.
fatal: Authentication failed for 'https://github.com/halby24/agent-kit.git/'. Retry after GitHub quota recovery; for private
repositories, verify GITHUB_APM_PAT can read the repository.

No lockfile is generated, and although it says the transaction was rolled back, partially deployed files remain (agents 4/7, skills 4), so the next run reports 4 files skipped -- local files exist, not managed by APM.

Suggested fixes

  • Retry with a token on throttle: even with the anonymous quota exhausted, retrying authenticated would succeed
  • Allow disabling anonymous-first: for private repositories anonymous probes always 404, so the current behavior only wastes quota

Related issues

Environment

Item Value Source
APM CLI 0.33.0 apm --version
OS macOS Darwin 25.6.0 arm64 uname -a
git 2.54.0 (Apple Git-157) apm doctor
gh 2.102.0, git_protocol: ssh gh --version, hosts.yml
Auth state gh token valid, 4970/5000 remaining. No GITHUB_APM_PAT or similar env vars set gh api rate_limit, apm auth github.com --check (works)
Target repo halby24/agent-kit, private gh api repos/halby24/agent-kit --jq .private → true
apm.yml 1 dependency (git@github.com:halby24/agent-kit.git + path: presets/common + ref: main), targets: opencode confirmed against the real file
Downstream layout presets/common/apm.yml declares 11 items via git: parent: 4 skills + 7 agents confirmed against the real file

Activity

  1. changed the title [-]Referencing files in a private GitHub repository burns through anonymous GitHub API quota and hits the rate limit[/-] [+][BUG] Referencing files in a private GitHub repository burns through anonymous GitHub API quota and hits the rate limit[/+] on Oct 8, 2026
  2. danielmeppiel commented on Oct 8, 2026

    @danielmeppiel
    Collaborator

    Decision: approve

    Area: project

    Scope: Restore the existing private-GitHub individual-file download recovery path after API throttling. When the API says its quota is exhausted and APM switches to its existing Git fallback, a separate Git authentication challenge must be allowed to use the existing AuthResolver-selected credential. Correct the boundary between errors belonging to the current attempt and incidental errors from the preceding attempt; do not add another authentication mode or fallback mechanism.

    Done when: Reproduce the combined path with multiple private file dependencies, including inherited git: parent references, anonymous API exhaustion, a subsequent Git authentication challenge and successful retrieval using a valid existing resolver-selected credential such as gh login, without demanding a new PAT. Use the existing APMLifecycle infrastructure with independent deployed-file and lockfile assertions, including exact file/ref provenance. Preserve public anonymous success without credential resolution, rejected-credential failures, real wrapped throttles and network failures, explicit SSH selection, host/repository credential isolation and bounded retries. Keep the canonical AuthResolver and typed throttle owner, existing private-cache and localized-error behavior, and diagnostics that retain the prior failure without letting it incorrectly govern the new operation. Cover the composed transition rather than relying only on isolated helper tests, and keep authentication guidance aligned with the repaired behavior.

    Out of scope: An anonymous-first opt-out or implicit adoption of #2545/#2999; a new credential provider or fallback chain; rotating credentials around API limits, retrying every 403/429, ignoring genuine backoff, stripping all nested error context, or broad transport/auth redesign. Repeated anonymous-probe optimization and the reported rollback residue remain unapproved observations for separate reproduction and scope. This acceptance covers only the recovery slice: do not close the whole report as fully fixed without an explicit disposition of its remaining observations. No implementation dispatch from this decision session, blocked-delivery restart, assignment, PR/merge approval or release commitment.

    Review contact: Daniel Meppiel (@danielmeppiel)

    Reason: This restores an existing promise rather than inventing a feature. #2284 added the bounded Git fallback after API throttling; #2406 made public GitHub access anonymous-first while allowing real authentication challenges; #2534 repaired another case where misclassification prevented a valid login from being tried; #2722 repaired private-cache authentication and expanded the shared classifier to inspect exception chains. Current-code inspection and an isolated no-network reproduction show that the earlier API throttle can incorrectly suppress the later Git credential retry. That evidence does not establish every symptom of the reporter's real installation, so the complete path still needs reproduction. Preserve each earlier repair instead of reverting it. Recorded by Copilot after Daniel Meppiel (@danielmeppiel) explicitly accepted this bounded proposal, with Daniel as review contact; this agent-authored explanation may contain errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

status/acceptedHuman scope approval; verify the issue's approval record and review contact before work.type/bugSomething does not work as documented.

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions