Skip to content

fix: make translation workflow work without a Copilot plan - #80

Merged
g4bri3lDev merged 2 commits into
feat/clean-portfrom
feat/translations-auth
Jul 27, 2026
Merged

fix: make translation workflow work without a Copilot plan#80
g4bri3lDev merged 2 commits into
feat/clean-portfrom
feat/translations-auth

Conversation

@g4bri3lDev

Copy link
Copy Markdown
Member

The scheduled run after #76 merged failed with 403 Forbidden. Everything up to the API call was correct, including finding the 18 new melody strings and chunking them, but the request itself was rejected.

Cause

GitHub Models access is a Copilot entitlement, and a repository GITHUB_TOKEN carries the organisation's entitlement rather than that of whoever triggered the run. This org is on the free plan with 0 Copilot seats, so the built-in token cannot reach GitHub Models however the workflow permissions are set. There is also nothing to switch on: the Models section does not appear in organisation settings at all, because the entitlement is missing.

This was missed because local testing used a personal access token backed by a personal Copilot entitlement. That validated the translation logic but proved nothing about CI, since the two are different identities.

Fix

GitHub Models and OpenRouter speak the same OpenAI-compatible chat/completions shape, so the endpoint, key and model become a small provider table. Whichever API key is present decides which is used:

Variable Provider
OPENROUTER_API_KEY OpenRouter. Preferred: no Copilot dependency and no output-token cap.
MODELS_TOKEN GitHub Models, via a personal access token with models:read.
GITHUB_TOKEN GitHub Models, only if the repository's organisation has a Copilot plan.

TRANSLATE_PROVIDER and TRANSLATE_MODEL override the choice for one-off runs, which also makes it easy to A/B a cheaper model.

OpenRouter costs roughly $0.12 for a full nine-language backfill. That backfill is already done, so ongoing cost is a fraction of a cent per year.

Also in this PR

  • API failures now print the response body and a concrete next step, instead of a bare traceback. That is how this diagnosis took minutes rather than guesswork.
  • Drops the models: read permission, which was doing nothing, so the next reader does not assume it is load-bearing.
  • The README's translation section keeps user-facing guidance visible and collapses maintainer detail into a dropdown.

Setup

Requires an OPENROUTER_API_KEY repository secret (already added). A repository secret rather than an organisation one keeps it readable only by this repo, and because the workflow is push-triggered rather than pull_request-triggered, fork PRs never receive it.

Verification

  • Provider resolution across 6 environment combinations, including the TRANSLATE_PROVIDER and TRANSLATE_MODEL overrides.
  • Live end-to-end run through the github provider.
  • The no-key error path.
  • verify_translations.py still passing on all 9 languages.

The run after PR 76 merged failed with a bare "HTTP Error 403: Forbidden"
traceback, which says nothing about why or what to do. The endpoint
explains itself in the response body, so read it and print it.

For 401 and 403 specifically, spell out the actual cause: a repository
GITHUB_TOKEN inherits the organisation's GitHub Models access, not that
of whoever triggered the run, so 'models: read' in the workflow is
necessary but not sufficient and an org owner has to enable Models. A
personal token working locally is not evidence that CI will work, which
is exactly how this got missed.
The scheduled run failed with 403 because GitHub Models access is a
Copilot entitlement, and a repository GITHUB_TOKEN carries the
organisation's entitlement rather than the triggering user's. This org
is on the free plan with no Copilot seats, so the built-in token cannot
reach GitHub Models however the workflow permissions are set, and the
org settings offer nothing to enable. A personal token working locally
was never evidence that CI would work.

Both GitHub Models and OpenRouter speak the same chat/completions
shape, so the endpoint, key and model are now a small provider table.
Whichever API key is present decides which is used, OPENROUTER_API_KEY
first; TRANSLATE_PROVIDER and TRANSLATE_MODEL override for one-off runs.
OpenRouter needs no Copilot entitlement, has no 4000-token output cap,
and costs roughly twelve cents for a full nine-language backfill, which
is already done.

Drops the now-pointless 'models: read' permission so the next reader
does not assume it is load-bearing.
@g4bri3lDev
g4bri3lDev merged commit ac7a176 into feat/clean-port Jul 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant