Tokens and permissions

Scope credentials to the capability that uses them.

Every token-bearing surface in Goobers — the repo token, a per-capability credentials: grant, daemonIdentity, a webhook secret — is a reference, never an inline value: token.env, token.file, token.keychain, or token.store. Capability admission only holds as a real security boundary if the underlying tokens are actually scoped narrowly. A single all-powerful PAT reused for every capability grant makes that boundary a formality.

Prefer GitHub's fine-grained personal access tokens over classic PATs. Classic PATs grant access to every repo the user can see under a coarse scope; a fine-grained PAT is scoped to specific repositories and specific permissions.

One token ref per capability, not one token for everything

Mint a separate fine-grained PAT per capability class your gaggle's workflows actually use, and register each as its own named ref in instance.yaml:

Capability Recommended fine-grained permissions Notes
github:issues:read Issues: Read-only Backlog polling, triage stages.
github:issues:write Issues: Read and write Create, claim, comment, ordinary-label, close.
github:milestones:write Issues: Read and write Assign an existing milestone to an issue — keep roadmap mutation out of stages that only perform ordinary issue writes.
github:issues:approve Issues: Read and write Applies the goobers:approved trust decision. Keep out of workflow stages unless self-approval is intentional.
provider:pr:write Pull requests: Read and write, Commit statuses: Read-only, Actions: Read Provider-neutral pull-request stages such as ci-poll. GitHub's fine-grained PAT UI has no separate Checks permission, so a private repo whose CI is entirely GitHub Actions-based is otherwise unreadable on this token type — also granting Actions: Read lets CI-status checks fall back to GET /actions/runs on that specific 403.
github:pr:write Pull requests: Read and write, Contents: Read and write GitHub-specific stages that open or update PRs.
github:pr:review Pull requests: Read and write Submit native approve/request-changes reviews. Source this from a different GitHub identity than github:pr:write for goober-authored PRs — GitHub forbids self-approval.
repo:push Contents: Read and write Branch + commit + push. The broadest local-tier grant; scope it to the exact target repo(s), never an org-wide token.
repo:clone Contents: Read-only Curation and analysis stages that never push.
configrepo:read Contents: Read-only Runner-only access to the workflow-config repo — configure only through workflowSource.token; stages cannot declare or source it through credentials.
agent:model Stored Copilot CLI sign-in, or (Account permissions) Copilot Requests: Read-only for headless use Copilot model authentication for agentic stages. An existing per-user CLI sign-in is the local default; a configured PAT is injected as COPILOT_GITHUB_TOKEN.
Repository access: select "Only select repositories." List exactly the gaggle's target repo(s) when minting a fine-grained PAT — never "All repositories."
repos:
  - provider: github
    owner: your-org
    name: your-repo
    token:
      env: GOOBERS_GITHUB_REPO_TOKEN # Contents: read-only
credentials:
  - capability: github:issues:write
    token:
      env: GOOBERS_GITHUB_ISSUES_TOKEN # Issues: read and write
  - capability: github:pr:write
    token:
      env: GOOBERS_GITHUB_PR_TOKEN   # PR/Contents: read-write; commit statuses: read-only
  - capability: repo:push
    token:
      env: GOOBERS_GITHUB_PUSH_TOKEN # Contents: read and write
  - capability: agent:model
    token:
      env: GOOBERS_COPILOT_TOKEN     # Copilot Requests: read-only; no repo access

Each credentials: entry sources one capability from its own token ref; an entry for a capability the repo token would otherwise back overrides it, so an issues-only stage never receives a token that also carries code or PR authority. Omit overrides for capabilities no selected workflow declares.

daemonIdentity: one distinct bot identity for authored PRs, reviews, and merges

One instance.yaml block backs the whole daemon-mutation capability set — repo:push, github:issues:write, github:pr:write, github:pr:review, github:branch:delete, github:pr:merge — with one distinct machine-account identity, instead of repeating a credentials: entry per capability:

daemonIdentity:
  kind: pat
  token:
    env: DAEMON_GITHUB_TOKEN

Mint the machine account's fine-grained PAT with the union of the permissions those capabilities need, never the operator's own token. Every daemon-authored PR, review, and merge then carries that account's login instead of being indistinguishable from the operator's own manual activity. An explicit credentials: entry for any one of the six capabilities still overrides daemonIdentity for that capability alone. kind: github-app reuses the same installation-token minting a repo's own auth.kind: github-app uses.

Why a second identity exists at all: GitHub forbids self-approval. A verdict posts as a native GitHub review only when the reviewing identity differs from the PR's author; on a single-identity instance every daemon-authored PR hits GitHub's self-review rejection and degrades to a documented comment/label handoff that the merge stage reads instead.

Agentic stages: stored login or the agent:model token

The permissions above cover the ordinary GitHub API. They are not sufficient for an agentic stage (any stage whose goober uses the Copilot or Claude Code harness) — the Copilot CLI authenticates to its model backend independently of repository credentials. For an interactive local daemon, sign in with copilot once; Goobers passes only the profile-location variables needed to find that stored session.

For a headless Windows Service, CI runner, or dedicated account without a stored session, configure a separate fine-grained PAT with Copilot Requests: Read-only — an account-level permission grantable only on a personal fine-grained PAT, never one scoped to an organization's repositories. When the target repo lives in an org, the agent:model token is necessarily a different token from the repo credential, with no repository access at all. An org owner must approve personal fine-grained PATs before they can access org repos, so budget for that approval step.

Also export the model token to the Goobers process. The harness auth preflight authenticates a clean CI/service profile from an ambient COPILOT_GITHUB_TOKEN — a token.env or token.file reference on the agent:model capability alone only reaches the eventual agentic stage, not daemon startup:
export COPILOT_GITHUB_TOKEN="$GOOBERS_COPILOT_TOKEN"
A mis-scoped token then fails during preflight rather than at the first agentic stage.

Verify harness availability before a live run with goobers validate --check-harness.

Token storage options

Every token-bearing configuration surface uses the same reference shape. Choose exactly one source:

Source Best fit Tradeoffs
env: NAME Ephemeral local runs and CI secret injection Most portable and simple, but present in the daemon's process environment; an already-running process does not pick up a changed shell or service definition.
file: /path Headless services with a mounted secret file Portable and easy to rotate in place. Goobers fails closed unless the file is private to its owner (Unix mode 0600, or an owner-only Windows DACL).
keychain: SERVICE Interactive or launchd-managed macOS runners Encrypted at rest by macOS and read for each resolution rather than retained. macOS-only; the daemon's user session must be able to unlock the login keychain without an interactive prompt.
store: STORE/SECRET Team and cloud deployments using a declared external secret store Keeps secret material out of local configuration and disk, with rotation managed centrally. Requires store identity/network configuration and uses the resolver's bounded cache rather than fetching on every use.

Create one macOS Keychain item per capability, using a unique service name:

security add-generic-password -U -a "$USER" \
  -s "goobers/github-issues" -w

Then reference only the service name in instance.yaml:

credentials:
  - capability: github:issues:write
    token:
      keychain: goobers/github-issues

Beyond a PAT

GitHub App installation tokens (auth.kind: github-app) mint a short-lived token per run instead of holding a long-lived PAT — no credential to rotate, escrow, or leak, and activity attributes to the App's own identity. See the product's token-scopes guide .

Azure DevOps repos authenticate through auth.kind: azure-cli for local development, workload-identity or managed-identity for unattended Kubernetes/Azure hosts, or a plain pat. See the ADO authentication guide .

External secret stores (Azure Key Vault today) let a token ref resolve through store: storeName/secretName instead of env/file, with rotation managed centrally. An instance that declares none behaves exactly as before.

Product source: docs/guides/github-token-scopes.md and docs/guides/ado-authentication.md.