Dev.to AI πŸ€– Ai πŸ‘ 0 πŸ“– 7 min read

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

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-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.

Three agents, three keys: each key mapped to its scopes, expiry and what a leak of it would allow
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

πŸ“° Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.