One scoped API key per coding agent
An API key per coding agent, scoped to the repositories and actions its job needs and set to expire, means one leak or one runaway loop is traced and revoked without touching anything else. On GitHub that means a fine-gr
An API key per coding agent, scoped to the repositories and actions its job needs and set to expire, means one leak or one runaway loop is traced and revoked without touching anything else. On GitHub that means a fine-grained personal access token or a GitHub App installation token; on Latchkey it is one API key per agent, created from an access preset with an expiry.

A key per agent turns "what did this token do" into a question with one answer, and makes revocation a local event.
The default is one token pasted into every tool: the CLI on the laptop, the agent in the editor, the bot in CI. It works until something goes wrong, and then nobody can say which of them ran the job, and revoking the token breaks all of them at once.
One key per agent fixes both. Before you mint one, answer four questions for that agent: what may it read, what may it start, what may it spend, and when should it stop working. The rest of this page is where each answer goes on GitHub and on Latchkey.
GitHub option 1: a fine-grained personal access token
A fine-grained token is limited to resources owned by a single user or organization, can be limited further to specific repositories, and carries specific permissions rather than broad scopes. Organization owners can require approval before one works. To dispatch a workflow, the endpoint needs the Actions repository permission at write.
GitHub lets you share a token template as a link with the fields filled in. expires_in takes 1 to 366 days or none; infinite lifetimes are allowed unless an organization or enterprise policy blocks them. Mint one link per agent role and keep it in your runbook.
Two catches. A token acts with its owner's identity, capped by its permissions, so a run it starts reads as yours. And fine-grained tokens cannot call the Checks API, which is why gh run watch says in its own help that it does not support them.
```Token template URL (line breaks for legibility)
https://github.com/settings/personal-access-tokens/new
?name=agent-claude-ci
&description=Dispatch+and+read+CI+for+one+repository
&target_name=acme
&expires_in=30
&actions=write
&contents=read
> Template parameters and the 1 to 366 day range from Managing your personal access tokens; the Actions write requirement from Permissions required for fine-grained personal access tokens; the Checks limitation from `gh run watch --help` in gh 2.102.0. All read 2026-11-12.
## GitHub option 2: a GitHub App installation token
An App gives the agent an identity of its own, and its installation tokens expire after one hour. When you mint one you can narrow it to named repositories and to a subset of the App's permissions; it can never exceed what the installation was granted.
Inside a workflow, `actions/create-github-app-token` does the minting. Each permission is its own `permission-<name>` input, and the README recommends listing them explicitly because by default the token inherits every permission of the installation. The token is revoked when the job completes unless you set `skip-token-revoke`.
```.github/workflows/agent.yml
steps:
- id: agent-token
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.AGENT_APP_CLIENT_ID }}
private-key: ${{ secrets.AGENT_APP_PRIVATE_KEY }}
repositories: api
permission-actions: write
permission-contents: read
- run: gh workflow run agent-check.yml --ref "$GITHUB_REF_NAME"
env:
GH_TOKEN: ${{ steps.agent-token.outputs.token }}
One-hour expiry and narrowing rules from Generating an installation access token for a GitHub App; inputs from action.yml at v3.3.0, tag SHA from
git ls-remote. Read 2026-11-12.
Which GitHub credential fits which agent
Pick by how long the agent lives and whose name its runs should carry.
| Credential | Lifetime | Acts as | Watch out for |
|---|---|---|---|
GITHUB_TOKEN |
Until the job finishes, or its maximum lifetime | The workflow | Its events start no run, except dispatch events and approval-required pull_request runs |
| Fine-grained PAT | 1 to 366 days, or none if policy allows | The human who made it | No Checks API, so no gh run watch
|
| App installation token | 1 hour | The App | Inherits every installation permission unless narrowed |
Latchkey: one key per agent, from a preset
Latchkey keys are minted in Settings, API keys and agents, by owners and admins. Name the key after where it will live, pick an expiry (Never is the default; 30, 60 or 90 days and 1 year are the others), and pick its access. Read-only is the default. Every write is a separate opt-in fixed at creation: an existing key cannot be upgraded, so a new need means a new key.
Keys are workspace-scoped, shown in full once, stored hashed, and revocable at any time; a revoked key loses access within a minute and stays listed for audit.
| Preset | Adds | A leaked key can additionally |
|---|---|---|
| Read-only (default) | mcp:read |
Read CI failure and run data, including log excerpts |
| CLI runner |
jobs:run (with jobs:read) |
Run commands on billable runners |
| Dispatch | mcp:dispatch |
Trigger any dispatchable GitHub Actions workflow in monitored repositories |
| CLI runner and secrets | secrets:write |
Set or replace any workspace or repository secret, never read one back |
| Approve runs | jobs:approve |
Approve a waiting run for any environment |
| Migration pull requests | migration:apply |
Open a migration pull request |
| Full access | Every scope, including workflows:run
|
All of the above, and start any Latchkey CI workflow |
Presets, scopes and expiry options from latchkey.dev/documentation/connect-your-ai-agent and security-and-permissions, read 2026-11-12.
Match the preset to the agent
A triage agent that reads failures needs Read-only. An agent that verifies its own edits needs CLI runner. An agent that re-runs a GitHub Actions workflow after a fix needs Dispatch, and nothing more. Keep Full access away from agents: Latchkey's docs point out that a key with jobs:approve held by an agent that can also push can approve the runs of its own changes, with no other person deciding.
Hand the key to the agent through its environment rather than a file. The CLI reads LATCHKEY_TOKEN, and a read-only status probe checks the key and its scope without creating anything.
# in the shell that starts the agent, one key per agent
export LATCHKEY_TOKEN=lk_live_... # key named "claude-code on build box", CLI runner, 30 days
latchkey status cli-00000000-0000-0000-0000-000000000000
# "Job cli-... was not found in this workspace." means the key works and carries jobs:read
The probe and its pass message are from SKILL.md in @latchkeydev/cli 0.16.0, read 2026-11-12.
See what each key ran
Attribution is the reason for all of this, so check it. Settings has an Agent activity tab that shows what each agent's key ran. In the terminal, latchkey list prints a KEY column naming the key that started each job, and the API keys table shows when each key was last used, flagging one that has gone unused for 30 days.
A key has no spending budget of its own. Bound an agent's spend through its work instead, with a batch budget named for the agent, as cap agent CI spend shows. When an agent starts getting 401s, the key was revoked or reached its expiry: mint a new one rather than widening an old one.
Documentation, not a captured session
No recorded run sits behind this page, and the repository's evidence files have nothing for it. The GitHub rows come from docs.github.com (personal access tokens, installation tokens, GITHUB_TOKEN) and actions/create-github-app-token at v3.3.0; the Latchkey rows from its connect-your-agent and security pages and the SKILL.md in CLI 0.16.0, all read 2026-11-12. A key minted for a demonstration would prove that one key works, not what a preset allows.
FAQ
Should I use a GitHub App or a personal access token for automation?
For long-lived automation, an App: its installation tokens expire after an hour, carry the App's identity rather than a person's, and can be narrowed per token. A fine-grained PAT is simpler to create and fits a single person's agent, but runs it starts are attributed to that person.
How long does a GitHub App installation token last?
One hour. The token response includes the time it expires, and the Octokit SDKs regenerate it automatically when it does.
Can a fine-grained personal access token trigger a workflow?
Yes, if it has the Actions repository permission at write on that repository, which is what the workflow dispatch endpoint requires. The workflow must also declare on: workflow_dispatch.
References
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.