Hermes plugin for using per-agent GitHub App identities instead of a human gh/SSH identity.
Each Hermes agent runs the same package but is configured with its own GitHub App:
github_app:
client_id: "Iv1.exampleclientid"
# Optional default used when a command cannot infer OWNER/REPO.
installation_id: "987654"
# Optional owner-specific installation IDs for apps installed in multiple orgs.
installation_ids:
ExampleOrg: "123456"
ExampleInfra: "987654"
private_key_path: "~/.hermes/secrets/agent-github-app.private-key.pem"
app_slug: "hermes-agent"Environment variables with the same meaning are also supported:
GITHUB_APP_CLIENT_IDGITHUB_APP_INSTALLATION_IDGITHUB_APP_INSTALLATION_IDS(JSON object orOWNER=ID,OWNER2=ID2mapping)GITHUB_APP_PRIVATE_KEY_PATHGITHUB_APP_PRIVATE_KEY(PEM contents; useful for CI)GITHUB_APP_TOKEN_BROKER=nono(no private key in this environment; see Running inside nono)
Repository access is controlled by the GitHub App installation scope in GitHub. If an agent should not access a repository, remove that repository from the GitHub App installation scope.
client_id identifies the GitHub App registration. GitHub recommends using the GitHub App client ID as the JWT iss claim when authenticating as an app.
installation_id identifies one installation of that app on a specific user or organization account. It is required when exchanging the app JWT for an installation access token via POST /app/installations/{installation_id}/access_tokens.
In other words: client_id answers "which GitHub App is signing this JWT?" while installation_id answers "which installed copy of that app should this token act as?" The same GitHub App can have multiple installation IDs if it is installed on multiple accounts. Configure installation_ids when one app is installed in multiple organizations; the plugin chooses an owner-specific ID from --repo OWNER/REPO or /repos/OWNER/REPO REST paths and falls back to installation_id when no repository owner is available.
pip install hermes-github-app-plugin
hermes plugins enable github-app
hermes-github-app setup
hermes-github-app doctor --repo OWNER/REPOsetup walks through the required values one by one. Optional prompts are explicitly marked with (optional):
GitHub App client ID:
GitHub App installation ID:
GitHub App private key path:
GitHub App slug (optional):
For scripted installs, pass flags and skip the network verification until secrets are mounted:
hermes-github-app setup --non-interactive --skip-verify \
--client-id Iv1.exampleclientid \
--installation-id 987654 \
--installation-id-for ExampleOrg=123456 \
--installation-id-for ExampleInfra=987654 \
--private-key-path ~/.hermes/secrets/agent-github-app.private-key.pem \
--app-slug hermes-agentdoctor checks local installation state and, unless --skip-network is set, verifies that an installation token can be minted and the optional repository probe is reachable.
hermes-github-app setup
hermes-github-app doctor --repo OWNER/REPO
hermes-github-app status
hermes-github-app token --repo OWNER/REPO
hermes-github-app api --repo OWNER/REPO /repos/OWNER/REPO
gh-app --repo OWNER/REPO pr list -R OWNER/REPO
git-app --repo OWNER/REPO push origin my-branchgh-app injects an ephemeral installation token as GH_TOKEN and GITHUB_TOKEN for the child gh process.
git-app injects a temporary askpass helper so HTTPS Git operations authenticate as the GitHub App installation token without writing credentials into the remote URL.
nono can run the agent in a sandbox that never holds the GitHub App private key. Enable broker mode with GITHUB_APP_TOKEN_BROKER=nono or github_app.token_broker: nono:
github_app:
client_id: "Iv1.exampleclientid"
installation_ids:
ExampleOrg: "123456"
token_broker: nonohermes-github-app setup --token-broker nono --client-id Iv1.exampleclientid --installation-id 987654In broker mode the plugin reads no private key; private_key_path and GITHUB_APP_PRIVATE_KEY* are ignored. Installation tokens are not minted locally. The plugin sends the placeholder nono-phantom-github-app as its token, and nono's supervisor, outside the sandbox, handles the real credentials:
- Its TLS-intercepting proxy replaces the
Authorizationheader on requests toapi.github.com,uploads.github.com, andgithub.comwith an installation token chosen by owner from the request path. Git HTTPS uses the same token as Basicx-access-token. - The app JWT is injected for exactly
GET /app.
What this means for agents:
github_app_*tools,hermes-github-app api,gh-app, andgit-appwork unchanged; the placeholder flows through.- Plain
git pushover HTTPS and plainghalso work, because the proxy injects credentials into those requests. Keep remotes HTTPS. SSH remotes use local SSH keys and are not covered. - The agent cannot obtain a real token, by design.
hermes-github-app tokenprints the placeholder with a note on stderr. client_idis still required in broker mode. It is non-secret and is reported in auth metadata.hermes-github-app doctorshows atoken brokercheck and anHTTPS_PROXY setcheck. If the proxy variable is missing, the agent is not running inside nono and no credentials will be injected.
To keep agents from falling back to local human credentials, update existing GitHub-related Hermes skills, cron jobs, and subagent prompts with these rules:
- Use
github_app_*tools for GitHub API operations when possible. - Replace authenticated
gh ...examples withgh-app --repo OWNER/REPO -- .... - Replace
git pushexamples withgit-app --repo OWNER/REPO -- push ..., or another HTTPS credential-helper flow backed by a freshly minted installation token. - Do not use
gh auth statusas proof of write identity; it reports localghcredentials and may show a human account. - Avoid SSH remotes for bot-managed worktrees. SSH uses local SSH keys, not the GitHub App token.
- Add a pre-write check with
github_app_verify_identityorhermes-github-app status --repo OWNER/REPO. - Avoid
@meassumptions because the GitHub App bot is not the human operator. - Require write summaries to include the returned
auth_mode,app_slug,installation_id, repository, operation, and URL/path.
The package is built with Hatchling and publishes through the CD GitHub Actions workflow using PyPI Trusted Publishing / OIDC. The workflow listens to all pushed tags but only builds and publishes when the tag matches:
^[0-9]+\.[0-9]+\.[0-9]+$
The tag must also match project.version in pyproject.toml.
Before the first release, configure PyPI Trusted Publishing for this repository and workflow:
- PyPI project name:
hermes-github-app-plugin - Owner/repository: this GitHub repository
- Workflow name:
cd.yaml - Environment name:
pypi
Release example:
git tag 0.3.1
git push origin 0.3.1Tags like v0.1.0, 0.1, or 0.1.0rc1 will not publish.
The plugin registers these tools:
github_app_statusgithub_app_verify_identitygithub_app_apigithub_app_graphqlgithub_app_create_issuegithub_app_comment_issuegithub_app_create_prgithub_app_comment_pr
All mutating tools return auth metadata showing App mode, installation ID, app slug, and target repository.