The obot portfolio previously ran on a dedicated GitHub machine account (obot-claw)
plus an always-on OpenClaw runtime — a second full identity with long-lived credentials to secure
and rotate. That setup was wound down in July 2026 (hub archived; see the
#7 design), leaving a gap: agents and automation still need a
non-personal identity with scoped access to the portfolio repos, so that
automated actions are attributable to the automation rather than to @jwildfire personally.
A GitHub App fills the gap with far less surface area than a machine account: tokens are short-lived (1 hour), scoped to an explicit repo whitelist, and minted from a single private key that Jeremy controls. Success criteria (from #3):
App registration (@jwildfire) ── App ID + private key (PEM)
│
│ sign RS256 JWT (≤10 min TTL)
▼
POST /app/installations/{installation_id}/access_tokens
│
▼
Installation token (1 h TTL, repo-whitelisted, permission-capped)
│
├── local agent: GH_TOKEN=$(obot-app-token) gh … / git push via x-access-token
└── Actions: actions/create-github-app-token@v2 → ${{ steps.app.outputs.token }}
Both consumers end at the same place: an installation token whose blast radius is capped three
ways — by expiry (1 hour), by the installation's repo whitelist, and by the app's permission
set (§3). Actions performed with the token are attributed to the app's bot login
(<slug>[bot]), so bot work is visibly distinct from @jwildfire's own.
| Actor | Identity | Examples |
|---|---|---|
| Jeremy working interactively; agent work Jeremy reviews before it posts | @jwildfire (existing gh auth — unchanged) |
Requirement drafting, PRs from working sessions, sign-offs, merges |
| Automation acting on its own; agent actions that should read as obot's | the app (<slug>[bot]) |
Scheduled workflows, cross-repo rollups, bot status comments, the future diary automation (separate requirement) |
The AGENTS.md attribution convention ("This … was drafted by Claude Code using …") continues to apply to the content of issues, PRs, and comments regardless of which identity posts them.
Bot-attributed git commits use the standard app-bot author format, with the bot user ID looked up
once after registration (gh api '/users/<slug>[bot]' --jq .id):
git -c user.name='<slug>[bot]' \
-c user.email='<ID>+<slug>[bot]@users.noreply.github.com' commit …
git push https://x-access-token:<TOKEN>@github.com/<owner>/<repo>.git
| Field | Value |
|---|---|
| Owner | @jwildfire (personal account — no org required; registration is a personal-account action) |
| Name | obotclaw — actions read as obotclaw[bot]
(D1, resolved — §8; availability re-checked at registration) |
| Description | Automation identity for Jeremy Wildfire's obot portfolio (safety-graphics modernization) |
| Homepage URL | https://jwildfire.github.io/obot.roadmap/ |
| Webhook | Disabled — the app is an authentication identity only; no event subscriptions, no endpoint to host or secure |
| Where can this app be installed? | Only on this account |
| Installation | "Only select repositories": obot.roadmap,
safety.agent, safety.viz, gsm.safety,
safety-histogram. New repos are added to the installation explicitly, never
"all repositories". |
| Permission | Access | Why |
|---|---|---|
| Metadata | Read | Mandatory for all apps |
| Contents | Read & write | Clone, branch, commit, release assets |
| Issues | Read & write | File/comment/label issues, rollups |
| Pull requests | Read & write | Open PRs, PR comments |
| Workflows | Read & write | Bot commits that touch
.github/workflows/ — e.g. rolling out the anticipated
@claude PR-responder workflow (§9) across portfolio repos |
| Actions | Read | Workflow-run status and logs via the API, for cross-repo run monitoring |
| Discussions | Read & write | Added by @jwildfire at registration (2026-07-04) — bot participation in repo Discussions |
As registered (2026-07-04): App ID 4215246 ·
installation 144370633 (the five §3 repos) · bot login obotclaw[bot],
user ID 299836032 · commit author
obotclaw[bot] <299836032+obotclaw[bot]@users.noreply.github.com>.
D2 resolved to this broader set (the draft recommended a minimal one):
Workflows (write) and Actions (read) are requested up front so the anticipated
follow-on consumers — the @claude PR-responder, which needs its workflow file pushed
to each participating repo, and cross-repo automation that watches run status — don't each
trigger an edit-and-re-approve round-trip on the installation. The set is still bounded: no
administration, organization, or account permissions, and the webhook stays disabled.
Known limitation (verified against the GitHub App permissions reference, 2026-07-03):
apps have no permission for user-owned Projects (v2) — the Projects permission exists only
at the organization level. The app therefore cannot read the
obot Roadmap user Project, and the
site's ROADMAP_TOKEN PAT (project read) stays in place for Project-Status reads. The
app replaces the obot-claw identity, not that token.
| Item | Sensitivity | Storage |
|---|---|---|
| App ID, installation ID, bot user ID | Low (identifiers, not secrets) | Helper-script config (committed docs are fine) |
| Private key (PEM) | Secret — equivalent to the bot identity | Local: macOS Keychain via security add-generic-password -s obot-github-app,
stored base64-wrapped (raw multiline values don't survive a
security -w round-trip); the helper reads it with
security find-generic-password -w | base64 -d. Never in
dotfiles, repos, or plain files on disk.Actions: repo secrets OBOT_APP_ID + OBOT_APP_PRIVATE_KEY,
added only to repos that run app workflows (initially obot.roadmap). |
| Installation tokens | Short-lived (1 h) | Never stored; minted on demand, held in process environment only |
Local agents need installation tokens without hand-running the JWT dance. Recommended shape: a
small self-contained script plus a thin skill, both in
safety.agent (the agent
scaffold owns agent tooling; there is no obot.agent repo — that idea is shelved):
scripts/obot-app-token — ~40 lines of bash on openssl: read the
PEM from Keychain, sign a 10-minute RS256 JWT, exchange it for an installation token, print
the token. No third-party dependencies (the community gh-token extension was
considered and passed over — an extra dependency for something this small, and less
auditable).obot-identity) documenting when to act as the bot (§2.1) and the
two usage patterns:
GH_TOKEN=$(obot-app-token) gh api … # API / issues / PRs as the bot
git push https://x-access-token:$(obot-app-token)@github.com/…
Acceptance for the agent flow: from a normal working session, mint a token and post a comment as
the bot on a designated test issue — then confirm the comment renders as
<slug>[bot].
Workflows use the first-party
actions/create-github-app-token@v2:
- id: app
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.OBOT_APP_ID }}
private-key: ${{ secrets.OBOT_APP_PRIVATE_KEY }}
# owner/repositories: defaults to the current repo; widen per workflow as needed
- run: gh issue comment … --repo jwildfire/obot.roadmap
env:
GH_TOKEN: ${{ steps.app.outputs.token }}
app-smoke-test.yml in
obot.roadmap, workflow_dispatch-only — mints a token and posts a comment on
#3 as the bot. Proves the
GHA path end-to-end without inventing automation the portfolio doesn't need yet.@claude PR-responder (claude-code-action authenticated with this
app's token via its github_token input, so replies and commits post as
obotclaw[bot]) — each its own requirement, deliberately out of scope here;
this app is their prerequisite — and any cross-repo rollup that must write outside the
workflow's own repo.deploy-site.yml keeps the default
GITHUB_TOKEN (public reads + Pages deploy) and ROADMAP_TOKEN
(user-Project reads, §3.1).The requirement closes only when the old identity's credentials are dead, not merely unused:
| Item | Action |
|---|---|
| obot-claw personal access tokens | Sign in as obot-claw once, delete all PATs (@jwildfire — requires the account's credentials) |
Local gh logins |
gh auth status on Jeremy's machines; gh auth logout any
obot-claw session |
OpenClaw-era configs (~/Documents/obot, old runtime configs) |
Audit for embedded tokens; scrub any found |
| Actions secrets on archived obot-claw repos | Document as inert — archived repos cannot run workflows; no action possible or needed |
| The obot-claw account itself | Keep, parked with zero credentials — it anchors the archived historical record. Deleting it is out of scope (and would free the name for squatters). |
All four decisions were resolved by @jwildfire on 2026-07-04 in a working session (recorded on #3). Original recommendations kept for the record; D2 resolved differently than recommended.
obot, orangebot, and obot-claw (all existing accounts;
@jwildfire is not an obot-claw org owner, so its name doesn't qualify as "your own").
Recommendation: obotclaw — verified free as both an app slug and an account name on
2026-07-03; keeps the 🍊😺 brand continuity. Fallbacks (also verified free):
orange-obot, obot-hq.
obotclaw, as recommended; actions read as
obotclaw[bot]. Availability re-checked at registration time.@claude PR-responder follow-on (§9) and cross-repo run monitoring don't
each need an installation-approval round-trip. Webhook stays disabled.safety.agent — a
dependency-free scripts/obot-app-token script doing the work, and a thin skill
telling agents when and how to use it (§5).@claude PR-responder loop — long-running task handoff
where Jeremy discusses work with the bot via PR comments and
claude-code-action responds event-driven from GitHub Actions. Its own
follow-on requirement; this app supplies its identity (the action's
github_token input accepts a token minted from a custom app). D2's broader
permission set anticipates it.ROADMAP_TOKEN — impossible today (no user-Projects permission for
apps, §3.1).To be filed as Tasks on #3 after design sign-off; listed here to show the intended decomposition.
OBOT_APP_ID /
OBOT_APP_PRIVATE_KEY secrets → obot.roadmap. (D3)obot-app-token + the identity skill in safety.agent; acceptance: bot
comment posted from a working session. (D4)app-smoke-test.yml to obot.roadmap; acceptance: dispatched run comments on
#3 as the bot.