Repository navigation
bug: creating an agent from a public github: template fails when the resolved GitHub token is stale or rate-limited, although the clone needs no token #3725
Description
Summary
Creating an agent from a public github:owner/repo template fails with a 503 (TEMPLATE_METADATA_UNAVAILABLE) whenever the one GitHub token the platform resolves for the creator cannot read the template's template.yaml over the REST API, even though the repo is public and the clone itself would succeed without any token. The refusal message blames a rate limit and tells the admin to configure a platform token, although a platform token may already be configured and was never tried.
Installing an agent from a public repo has to be seamless on a fresh or lightly configured instance. Today it depends on the health of whichever token happens to sit first in the resolver chain.
Observed: on an instance with a platform GitHub token configured, creating github:Abilityai/agent-builder (public, listed in the template registry, readable anonymously) was refused:
Could not read 'github:Abilityai/agent-builder' template metadata from GitHub, so Trinity cannot tell whether this template must be copied into a repo you own. Refusing rather than guessing. This is usually a transient GitHub rate limit — retry shortly, or configure a platform GitHub token in Settings to raise the limit from 60 to 5000 requests/hour.
Context
- Tokenless creation of public templates in source mode is a supported flow (refactor: Continue database.py mixin split (1270 lines) #123 lineage): the anonymous ls-remote probe and the clone need no credential.
- Before the clone, the fork-to-own gate (trinity-enterprise#14 S2) reads
template.yamlthrough the GitHub contents API to learn whether the template requires a copy into the creator's own repo. That read is fail-closed on any answer other than a clean 404, by design. - The read uses the single token the resolver picks (per-agent, then the creating user's own token, then the platform token). There is no anonymous retry and no fallback to the next tier. A stale or revoked user-level token therefore yields HTTP 401 for every repo, public ones included, and the gate refuses. A spent anonymous budget (no token at all, with the registry catalog consuming the 60/hour allowance on the same egress IP) yields HTTP 403 and the same refusal.
- The error text sends the admin to the platform-token setting, which may already be set and not the token at fault. The log line that carries the real reason (
Failed to fetch template.yaml for <repo>: HTTP 401) is not surfaced.
Acceptance Criteria
- Creating an agent from a public
github:owner/repotemplate in source mode succeeds when the repo and itstemplate.yamlare readable anonymously, regardless of whether the resolved GitHub token is missing, expired, revoked, or rate-limited. - The fork-to-own gate still refuses (fail-closed) when the template genuinely cannot be read by any credential-free or credentialed path, and still refuses a private template that only an unavailable token could read.
- A stale user-level token does not mask a valid platform token: when the preferred tier's read is rejected with 401, the next tier (or the anonymous path for a public repo) is tried before refusing.
- The refusal message names which token tier was used and the HTTP status GitHub returned (for example
user token: HTTP 401), so the admin is pointed at the right setting instead of a generic rate-limit hint. - Regression test: public template, user-level token set to an invalid value, platform token valid → creation succeeds. Public template, no token at all, anonymous API rate-limited → creation succeeds via the credential-free path. Private template, no usable token → refused with the token tier named.
- Docs:
docs/user-docs/agents/creating-agents.mdstates that public templates install without a GitHub token and which token is used when one is present.
Technical Notes
- Gate and resolver:
src/backend/services/agent_service/crud.py(_resolve_github_repo_and_pat,_read_source_template, theTEMPLATE_METADATA_UNAVAILABLEbranch in_apply_fork_to_own). - Fetch and the unreadable classifier:
src/backend/services/template_service.py(_fetch_template_yaml_result,metadata_reason_is_unreadable). - The anonymous repo probe already used later in the same flow (
probe_anonymous_repo_access) establishes "this repo is public" over the git transport, which is not subject to the REST rate cap.
Activity
fix(agents): checkpoint A — FR-7 requirement, tier ladder, ref probes…
fix(agents): checkpoint B — SHA-pinned raw template read + token-reje…
fix(agents): checkpoint C1 — async template-read chain: remaining tie…
fix(agents): checkpoint C2 — identity follows a 401-rejected token, p…
test(agents): checkpoint C3 — ent14 fixture stubs the #3725 seams; ou…
test(agents): checkpoint D1 — identity, 403 re-check and disclosure r…
docs(agents): checkpoint D2 — public template metadata reads need no …
test(agents): fakes of _apply_fork_to_own accept attempts (#3725)
fix(agents): review follow-ups — docs pointer, unused import, re-gate…
vybe commentedon Oct 14, 2026
Draft PR: #3736 (branch
feature/3725-public-template-tokenless-metadata, tip 550c212, base dev). Built by chain-3725 via the fleet engineer: plan → rulings T-a..T-h → four checkpoints → review (MERGEABLE AFTER FIXES, cso clean) + evidence sweep (mutations 5/5 caught) → fix. Merge is the operator's call.vybe commentedon Oct 14, 2026
chain-3725 closed: PR #3736 validated APPROVE by the engineer (#3736 (review)) on tip 550c212 with every CI check green; the PR is now ready for review. Merge is the operator's call. Handoffs after merge:
/sync-feature-flowsfortemplate-registry.md/template-processing.md(still describe the single-remedy 503); theimport_intent: copypath carries the same stale-token exposure (follow-up issue to file).fix(agents): public github: templates install when the resolved GitHu…