This is a fork of cgwalters/homegit
used as the dotfiles and prompt repository for the
cgwalters-bot GitHub account, an agentic
bot that helps with upstream contributions. Commits made from this
environment are authored as Colin Walters <walters+llm@verbum.org> (see
bin/bot-git), and the agents' work is
coordinated through the Workstream
project board: a human moves items into Todo, the bot claims them, pushes
tested branches to forks in the cgwalters-forge
organization and proposes them there as draft PRs (Draft),
and only once cgwalters approves one does it go upstream (In Review);
only a human's acceptance makes something Done.
The accounts, org, board and identities named here are the defaults of
the operator config (~/.config/bot-harness/operator.json, printed by
bot-operator); another operator sets up their own as
docs/bootstrap.md describes.
The forge board and issues are the control plane, rather than Paperclip.ing's built-in tracker: we need the forge for collaboration anyway, so coordination belongs alongside the work and its review. The board is the operator's primary interface. Periodic summaries belong in its project status updates; chat replies are one line of status plus the update URL. Keep item fields and asks current on the board so the summary points to the work and the decisions where they can be acted on. Agent identity remains an open question: a GitHub App per role (coordinator, engineer), or labels on the board to enforce access control. Paperclip avoids the question with its own identity system.
The direction is sandboxed, forge-native devspace runners with separately validated and applied safe-outputs, moving toward gh-aw. Devspace agent runs describe the current contract; the scheduled dispatcher is the planned move from a local controller session to scheduled and event-driven Actions jobs. Which runners may be used for what is the CNCF-runner rule.
Longer term, a standalone Forgejo + Actions deployment with a control-plane UI could run on Podman Quadlets or Kubernetes.
Install with make install as usual. The shared agent prompt is still
AGENTS.md. The skills are plain Agent Skills
in dotfiles/.agents/skills, installed as ~/.agents/skills where
opencode, Codex, Gemini CLI and Cursor look; Claude Code reads only
~/.claude/skills, so dotfiles/.claude/skills has a symlink to each
(tests/skills.sh checks they match). They are workstream (using the board),
backlog-planning (refilling the board from recent GitHub activity),
bot-feedback (surfacing reactions on the bot's work), bot-notify
(routing mentions of and requests to the bot),
upstream-pr (how to contribute as the bot, with bot-pr), devspace-work (building
and testing on a remote runner), coordinator (running a top-level
session that polls, dispatches worker and reviewer subagents, and writes
project status updates; the preambles it briefs them with live next to it),
topic-lead (a session of its own for one topic, which owns the board items
whose Lead field names it and coordinates with the coordinator only through
the board and issues), plus the upstream ones like
commit-review.
The bot uses two agents. opencode is configured for the Praxis proxy, which
supplies its authentication. Claude Code uses a Claude subscription; for
headless runs generate a long-lived token with claude setup-token and
export it as CLAUDE_CODE_OAUTH_TOKEN. (Anthropic subscription credentials
must not be used through opencode.) Both need the gh CLI logged in as
cgwalters-bot, either via gh auth login or a GH_TOKEN, with the
project scope in addition to the usual repo access.
To run a single unattended session, use bot-work, which by default runs
opencode and picks up or advances the next board item; bot-work --agent claude uses Claude Code instead, and any extra arguments are passed along
as additional instructions. bot-work --plan instead reviews recent GitHub
activity (the last week, or --since YYYY-MM-DD) and adds candidate items
to the board for triage. Only one session runs at a time, across all
machines: besides a local lock, bot-work holds a lease in an archived
board item (bot-state: lease) while the agent runs, which lapses 20
minutes after its machine stops renewing it.
People often react to the bot with an emoji rather than a comment, so
the bot-feedback skill has an agent review each new ๐ or ๐ on
everything cgwalters-bot wrote and file it, with an assessment, as an
issue on cgwalters-bot/cgwalters-bot
for cgwalters; backlog-planning runs it first, and a scheduled job to
run it is planned.
Likewise bot-notify polls the bot's notifications: a mention, review
request or assignment by cgwalters becomes a Todo item on the board,
while one by anyone else is filed as an issue for cgwalters and never
acted on. GitHub doesn't reliably notify the bot, so it also checks
cgwalters' public events and searches for mentions. Its poll state lives
on the board itself, in an archived draft item (bot-state: notifications), so any machine can pick up where the last run stopped.
Agents run it at the start of each session and planning pass, and
bot-sweep.timer runs it on a schedule as described below.
Pings are only part of it: bot-watch sweeps the issues and PRs of the
items on the board and reports what changed on each since it last
looked (new comments and reviews, merges and closes, pushes by others,
CI going red), and with --apply, when it sees an item's upstream PR
merge or close, moves it to Done or Needs human (from Todo, Draft or In
Review only). Its last-seen state is another archived item,
bot-state: watch. Every sweep also lists the bot's PRs that just need
a rebase (behind their base with CI failing, or conflicting); bot-pr rebase URL does that for a conflict-free one, keeping my sign-off, and
the coordinator runs it on its own. And it starts with the "Priority
health" of the P0 and P1 PRs (bot-priority-health): failing or stuck
CI, conflicts, missing sign-offs, and PRs nobody has touched in a day
(P0) or three (P1), so that a red P0 PR can't sit unnoticed. Its "P0
drive" (bot-drive) re-derives each bot P0 PR's merge blocker on every
sweep, since a PR that falls behind again raises no event, and rebases
a conflicting PR, or a behind one nothing else blocks, at most hourly.
Its "Operator activity" (bot-operator-activity) reads my own GitHub
activity (my public events feed, and the bot's notifications for
private repositories), keeps only what touches the bot's work (its
issues and PRs, board items, threads it commented in), and has a
small model classify each such event once, so a review or reply of mine
that no edge-triggered rule caught still wakes the coordinator. With
--apply, it also runs bot-pr signoff on the bot's upstream PRs whose
DCO check fails although I approved their head (bot-signoff-due), so
my approval is all a sign-off takes, and bot-pr promote on the fork
PRs whose head I approved for a bot-ok repository (bot-promote-due),
so my approval is all a promotion takes too; no model is in that loop.
For a human-text repository it promotes only on my /promote --human-text, once the text is mine. When an approval of mine can't
promote (the repository is human-text and I only approved, its policy
record is missing or stale, the head moved since, or bot-pr promote
refused), it comments once on the fork PR with the reason and my next
step, and notes it in the board item's News; a new approval that still
can't promote gets a new answer. Before promoting (or holding) a fork
PR, it also comments there once per policy record with the upstream's
LLM rules as the record quotes them: advisory, blocking only where they
disallow bot accounts (no-go). And it keeps one tracker issue with its
view of each upstream's policy (verdict, what the check said, the last
upstream PR it promoted there), marking a record whose sources changed
since for a recheck.
The coordinator doesn't run these by hand: bot-sweep, run every 10
minutes by the bot-sweep.timer systemd user unit (in
dotfiles/.config/systemd/user), sweeps with bot-watch, bot-notify
and bot-pr inbox apart from any agent session, and publishes each
completed sweep under ~/.local/state/bot-sweep. bot-poll --from-sweep
(Rust, in crates/, installed by make install) reads them and exits
only when something is new since it last reported (an approval, a
review, a new P0 health problem, ...), or when the sweeps fail or stop,
which wakes the coordinator; bot-poll --summary lists what's new. Between sweeps it polls a hot
set (the bot's open PRs, my open asks, what I touched lately) every 90
seconds with conditional requests, so my approvals and answers wake the
coordinator within a couple of minutes, while a quiet cycle costs two
304s (and a few more per item I'm active on).
Local Claude workers run with bot-claude start (or run, which also
waits). Their default prompt requires noninteractive completion and
foreground, blocking waits for dispatched work and verification.
bot-claude wait JOB returns 5 for an incomplete job: Claude exited
successfully but its final report promises further work, or its stream
still shows outstanding background tasks. Failed runs remain failed.
Use bot-claude resume JOB, then bot-claude wait JOB, to continue the
saved Claude session with the original options. Resume rejects running
or successful jobs and jobs without a session id; it archives prior
evidence under the job's attempts/N/ and resets current results.
Local agent sessions run on a trusted machine that holds the clones and
the bot's credentials, and borrow compute for building and testing;
dispatched agent runs use sandboxed devspaces instead. bot-devspace
(installed by make install like the rest of bin/) dispatches an
ephemeral RHEL 10 runner from
bootc-dev/cgwalters-devspace-sandbox,
waits for it to be reachable over the tailnet and hands out an
ssh_config for its unprivileged runner-sandbox user (the workflow installs the
toolchain: podman, gcc, Rust, bcvk, ...), so an agent edits
locally, pushes its branch to the devspace with plain git over SSH, runs
the project's tests there (KVM is available for VM tests) as detached jobs
it blocks on with bot-devspace wait, and pushes the
tested branch to the forge fork from the local machine. No credentials are
ever copied to a devspace, and the SSH user can't reach the runner's
own (see the devspace-work skill); tests/bot-devspace.sh tests which
user bot-devspace picks and how jobs end. bot-devspace stop cancels the runner, since
they're billed while they run.
Dispatched runs put the agents themselves on devspaces: an agent.yml
workflow runs an agent on one board item, with its condensed transcript
in the Actions log and a summary artifact per run
(plan).
bot-runs, modeled on gh aw logs and gh aw audit, dispatches those
runs and reads them back (list, show, log, transcript, diff
and stats, over REST only). A dispatched run is recorded in its board
item's Run field, and bot-runs reconcile (on every bot-watch
sweep) moves the item on when the run is over. What a run leaves behind is specified in
docs/devspace-agent-runs.md, and
tests/bot-runs.sh tests the tool offline against fixtures that follow
it.
bot-cost estimates what all of this costs, per UTC day and per task:
runner time of the devspace and agent workflows (core-hours at GitHub's
published per-minute Linux runner rates, a notional figure for runners
that may be self-hosted), and inference tokens from the local
transcripts and agent runs' token-usage.jsonl, at the per-model prices
from models.dev. The coordinator puts yesterday's
estimate in the morning brief. bot-footer turns one task's share into
a run footer that fork PRs carry in their bot-meta section (see "Run
footer" in the same doc).
Planning is by cost and capacity. Items carry an Est. cost bucket (XS to
XL, in fresh inference tokens; see "Cost estimates" in the workstream
skill), once an agent works on them a Budget tokens number (bot-pace),
and their Actual tokens, which bot-actuals sums from
the transcripts of workers whose prompt has an Item: URL line.
bot-capacity shows the week's usage, burn rate and projection (from the
percent bot-heartbeat statusline saves) next to the open P0/P1
estimates, which the coordinator uses to decide what to dispatch.
Each inference pool is paced on its own, as a budget controller that
aims to have used pacing.pools.NAME.target (95%) of the pool's window
by its reset, evenly: at any time the pace allows target * elapsed / window, plus a few points of burst. The Claude subscription is read from
the status line's weekly window and the OpenAI Codex one from the praxis
broker's /usage. A pool whose usage is above its pace holds new work
routed to it and nothing else: Claude's the local workers and the apply
and review steps, OpenAI's the devspace runs. An item labeled urgent
goes anyway, and a pool without a reading is limited only by the agent
target. bot-capacity also records what a run costs each pool, which
says how many more fit.
The coordinator's loop is a controller: bot-reconcile compares the
observed state (the board, the latest sweep, the heartbeat, the pace of
each inference pool, answered questions) with the desired one and prints
the actions that close the gap, under rules in lib/reconcile.js: keep the operator
config's pacing.agents work agents busy, split between harness and
upstream work; keep the heartbeat in step with the board; carry over the
sweep's P0 drive and approvals; apply answered questions; set items Done
whose issue or PR closed (which bot-watch --apply does itself).
bot-poll-loop --until-actions
waits on the sweeps and wakes the coordinator on news and on new (or, after
2 hours, still open) actions, run as one background command that exits
only then (or TIMEOUT at its --max-wait, to be rerun).
bot-heartbeat publish puts the coordinator's loop state and its local
workers (names, board item links, devspaces) in one pinned comment on
the "Bot heartbeat" issue in cgwalters-forge/tracker, edited in place,
where the review app's ops view reads them as "Local agents"; it
validates the input against a strict schema and leaves out items in
private repositories. It also writes a usage snapshot, the ops view's
"Usage", but never to that public comment: to one comment on the
"Bot usage" issue in the private cgwalters-forge/bot-ops, which the
review app reads with the viewer's own token. The snapshot is the
plan's 5-hour and 7-day percent used and reset times, which
bot-heartbeat statusline saves as the Claude Code status line, and
the tokens local transcripts spent in each window and per worker
(aggregate numbers only). tests/bot-heartbeat.sh and
tests/bot-heartbeat-usage.sh test it offline.
bot-board is a small CLI over gh project for the Workstream board
(list, show, add, draft, set, and state-get/state-put for
the scripts' state items), which caches reads because the
bot's GraphQL quota is shared by every agent. The state items are what
lets any machine run a session: nothing a script needs to remember lives
only on one machine, and state-put --checked merges the changes of two
machines writing the same item at once. tests/bot-state.sh tests that
state handling offline, against a fake gh. Each item's Workflow field
says what the bot delivers: a tested branch proposed as a draft PR on a
forge fork (branch, the default), a write-up in a secret gist (analysis), a
draft PR straight upstream (pr, set only by a human), or nothing
(manual). In the morning, Draft items link to what's ready for review.
bot-board status-update creates a native GitHub project status update
and prints the project's URL. It defaults to Workstream; put --project BOARD
before status-update to target another board. The generated report has
Changed, In progress, and Waiting on the operator sections:
merges, closes, completions, promotions and changed News; active items
with agents or runs and upstream review; Draft reviews and Needs human
decisions/actions with Why and Branch/Gist links. Worker names and states
come only from the local heartbeat's privacy-filtered publication, with
activity (or start time) less than six hours old and an exact association
to an unfinished item in this project (item URL/ID or a Run/Branch URL).
Unmatched workers are omitted; Lead is the fallback for an In Progress
item without a matching worker. Waiting asks follow the documented board
statuses and labels, not exact review-app parity: that app is unavailable
locally. An open Needs human ask with a first-line Blocks: ITEM link
replaces its Needs human parent in the waiting section.
Each section shows at most ten one-line items in deterministic priority/title
order, followed by an accurate +N more count and a full-board link. Titles
and details are shortened; long or unsafe item URLs are omitted rather than
truncated (follow the board link). The stale-P0 footer shows at most three
titles and the remaining count. Generated posts, including their hidden
recovery marker, are limited to 3,800 characters, below 4,000.
Explicit posts can combine these flags:
--status on-track|at-risk|off-trackoverrides the derived project status.--since ISOchooses the change window; use a full ISO timestamp with seconds and a timezone, e.g.2026-10-03T00:00:00Z. By default the window starts at the last project post (or the successful local post if newer), or 24 hours ago when there is no post. Completion/promotion transitions and News comparisons also use the saved observation baseline;--sinceapplies timestamp filtering instead of the usual baseline-only checks. Day-only News without a reliable News field timestamp is excluded if the date's UTC midnight predates the explicit cutoff. The default window still retains same-day News without a baseline and compares text with one.--body FILEreplaces the generated text with a nonempty UTF-8 file, for a curated periodic summary. Status is still derived unless overridden, and the tool still appends its recovery marker and saves the observation.
Flags, the P0 threshold, and custom body readability, UTF-8 encoding,
nonempty content and the standalone size limit are checked locally before
project resolution or any API call. The full body size, including the
recovery marker, is checked after reading the board and before posting.
Custom --body posts retain GitHub's 65,536-character limit, including the
marker; the generated-post budget does not apply to curated text.
Without --status, an unfinished P0 is stale after 24 hours without observed
material movement: Status, Priority, Lead, Branch, Why/Gist or News content,
title/body/state changes, or the latest comment's content. Token counters,
Run changes, timestamps and News date-prefix churn do not reset this clock.
On first observation (or recovery from an older marker), project/content
timestamps provide the initial evidence; bookkeeping before that observation
cannot be distinguished from real activity and can delay risk detection.
Once a material baseline exists, generic updatedAt never overrides it.
Changes between observations that revert before the next read are not seen;
comment activity is limited to the latest comment, not all PR review activity.
Any stale item makes the project at-risk; otherwise it is on-track.
Done, closed and merged items are finished. BOT_BOARD_STATUS_P0_HOURS
changes this threshold
and must be a positive number. off-track requires an explicit override.
bot-sweep runs bot-board status-update --auto as its final phase after
the watch/notify/inbox and gc/actuals/fill-org phases, with a two-minute
timeout. Earlier step failures do not gate that phase. --auto cannot be
combined with --status, --since or --body: it skips if the latest
project update or successful local post was less than four hours ago,
including a human's project update, then skips if the material observation
is unchanged. The digest covers item fields, state, ask class, worker
names/item URLs/states and derived status; timestamps and the News date
prefix alone do not count. Aging a P0 into at-risk therefore counts as a
change. A skip prints status-update: skipped (four-hour throttle) or
status-update: skipped (no material change) and succeeds. Explicit posts
bypass both checks. A failed status-update step is a sweep problem but
does not by itself make the watch/notify/inbox news incomplete; its output
is in runs/ID/status-update.txt, not a latest-*.txt news feed. A successful
publisher's project URL is also printed in the sweep's final stdout.
Run one publisher on one machine per project. bot-board serializes local
posts with a per-project flock and stores status-update.json under
${XDG_STATE_HOME:-~/.local/state}/bot-board/KIND/OWNER/NUMBER/. Project
updates carry an HTML recovery marker with the digest and minimal item
baseline, including material fingerprints and movement times, when that fits
the posted-body budget. Otherwise the marker contains only the digest (marked
digest-only). The local snapshot always retains the full baseline. If local
state is lost after a compact-marker post, remote recovery still provides
automatic deduplication and throttling, but cannot infer completion/promotion
transitions or compare News against that post's per-item baseline. It uses
timestamp evidence for News and initial P0 inactivity, and never manufactures
transitions. Losing movement history can delay stale-risk detection after
timestamp-only activity. A subsequent successful local post restores the full
local baseline. A separate
status-update.json.observations.json records movement on every observation,
including auto skips, without losing the last posted transition baseline.
The marker lets the next run recover after a post succeeded but saving
the local snapshot failed. Reading those updates does not provide a
cross-machine lock or an atomic check-and-post: concurrent publishers on
different machines can still duplicate a post.
The GraphQL payload uses createProjectV2StatusUpdate with projectId,
status and body, returning statusUpdate { id body createdAt creator { login } project { url } }. These fields and the enum were checked against
the live schema by introspection.
The project URL avoids assuming a status-update url field or deep-link format.
bot-tmt-number hands out bootc's tmt test numbers (the # number: N
header that becomes plan-N and test-N), which concurrent bot branches
used to collide on. The next free number is one past the highest taken
on main, in the added lines of every open PR upstream and on the forge
fork (read with conditional REST requests, each PR rescanned only when
its head moves), and in the reservations workers make with --reserve BRANCH before they open a PR. Those live in the tmt-numbers board
state item rather than in a committed file: a reservation has to be
visible to the other workers within seconds, and landing a homegit PR
takes minutes of CI, while a strict checked state-put (retried when
another machine wrote meanwhile) plus a local lock gives that at the
cost of one GraphQL call each. --gc releases a reservation once its
number is on main or in its branch's open PR, or its PR closed;
tests/bot-tmt-number.sh tests it offline against a fake gh.
The bot doesn't open upstream PRs on its own. A finished branch item is
a draft PR from bot/<slug> into the upstream base branch of a fork in
cgwalters-forge (cgwalters-forge/REPO),
linked from the item's Branch field, whose title and description are
already written as the upstream PR. A trailing section between
<!-- bot-meta --> markers names the upstream repository and base
branch, and the board item. On that PR:
- Approve it, or comment
/promoteon a line of its own, to have it opened upstream, ready for review. Other wording ("go ahead", "ship it") is not an approval; the bot's inbox flags it so you get asked for/promote. - Add a
/draftline (in the/promotecomment, or earlier) to have it opened upstream as a draft (a later/readytakes that back). - Comment (inline or on the PR) to ask for changes, including commit
message rewording; the bot answers with squashed fixups and a reply.
An approval or
/promotecovers the commits pushed before it, so changes pushed after it need another one. - Edit the title and description as you like: they are copied upstream as they stand at approval, minus the bot-meta section.
- Close it to drop the change; the item becomes Done as dropped.
Forge forks start with a clean main synced from upstream, and both
accounts can push to them. They run no CI: every workflow is disabled
(bot-pr fork-setup), because the forks share one runner pool that a
single bootc PR's matrix fills, and the bot tests every change on a
devspace, whose results the PR description gives. Upstream CI runs once
the PR is promoted. A change to a workflow itself is exercised by opting
that workflow in (bot-pr fork-setup REPO --ci FILE, undone with
--no-ci). A fork is either a midstream (the default: repository
topic bot-midstream, main mirrors upstream, its PRs are drafts that are
never merged, and fork-setup adds a ruleset that blocks merging them) or
a real, possibly temporary, fork (bot-fork, set with
bot-pr fork-setup REPO --fork), whose main may carry its own commits.
The bot's
personal forks (cgwalters-bot/REPO) are only for scratch work.
bot-pr implements the bot's side: fork-pr creates the
forge fork if needed and opens the fork PR, get-body and set-body
update its description without overwriting your edits, refresh-meta
brings its bot-meta section up to date when the template changes,
inbox lists your activity on them since its last run (only the cgwalters login
counts, so the bot's own edits, pushes and comments are left out), and
promote rebases onto upstream, opens the
upstream PR, closes the fork PR and moves the item to In Review. Agents
run bot-pr inbox at the start of every session. What inbox has already
reported is kept in the archived bot-state: pr-inbox board item, so it
doesn't matter which machine runs it.
Every forge PR also gets a review guide from the reviewer subagent that
checked it: the ordered hotspots worth reading closely (a head-side line
range, a severity, a category and a one-sentence reason) and what is
safe to skim. bot-review-guide prints what a reviewer needs to write
one (context), validates it against the PR (check), posts it as a
COMMENT review with the JSON in an HTML comment (post) and reads back
the latest one (show). The review app
trusts only the bot's guides, and only for the head they name, walks
their hotspots and tints them in the diff; tests/bot-review-guide.sh
tests the tool offline.
The bot's own repositories (this one, cgwalters-bot/cgwalters-bot,
debug-bootc-to-disk-virtiofsd, ostree-missing-refs and
praxis-credential-broker, plus cgwalters-forge/review,
cgwalters-forge/agentic-job and cgwalters-forge/.github) take changes
only as pull requests: a ruleset on main requires one with green
required checks, up to date with main, and linear history, with no
bypass, and blocks force-pushes and deleting main. They only allow
rebase merges, to keep the commits as written, with auto-merge on and
branches deleted on merge. bot-land lands a branch: it pushes it,
opens the pull request, enables auto-merge and waits, rebasing when main
moved on. With --base PARENT (and --no-auto) it stacks a branch on
another unmerged one, and once the parent merged it retargets the pull
request to main, dropping the parent's commits. A rebase merge re-commits the commits unsigned, with the
merging account (the bot) as committer. Here the ci workflow
(.github/workflows/ci.yml) runs shellcheck, node --check, the
crates' checks and every tests/*.sh in parallel jobs, and the ruleset
requires only its required-checks job, a gate that fails unless every
other job succeeded or was skipped by its own condition, as bootc's CI
does: jobs can be added, split or made conditional without touching the
ruleset (tests/ci-gate.test.js checks that the gate needs them all and
runs always()). Other repositories may require just a ci job;
bot-land waits for whatever checks the ruleset requires.
Some projects accept AI-assisted code but want the PR text to be a
human's, and some want no AI contributions at all. So promote and
signoff first run upstream-policy check OWNER/REPO, which reads the
record in upstream-policy/OWNER/REPO.md here: a verdict (bot-ok,
human-text, human-only or no-go), the AI trailer and DCO
expectations, and the blob ids of every policy file it was based on
(CONTRIBUTING, AGENTS.md, PR templates, AI policies, the org's .github
defaults), with the quotes and rationale in its body. They refuse
unless the record is committed, every source is unchanged on upstream's
default branch, no new policy file has appeared, and the verdict is
bot-ok. A separate read-only subagent writes the records
(dotfiles/.agents/skills/coordinator/policy-check.md); to overrule
one, I have the bot open a pull request that edits its verdict, and
approve it. Records count only as merged to homegit's main, and a
commit there that loosens a verdict (say human-text to bot-ok) must
come from a pull request whose merged head I approved (or be one of
mine that GitHub shows as verified), so the bot can record and tighten
verdicts but not loosen them: a record's verdict
may be no looser than the strictest it held since I last loosened it,
across edits, deletions and moves (git's rename detection, and paths
that differ only in case), so anyone else's loosening blocks the gate
until it's tightened back. Records must be regular files, not symlinks.
A ruleset on this repository blocks force-pushes to main and deleting
it (and takes changes only as pull requests, see below), and the
check also refuses a main that doesn't descend from the last one it
accepted (kept in ~/.local/state/upstream-policy/), since rewritten
history could make a loosened record look newly created.
For human-text, I retitle the fork PR, rewrite its body and reword
the commits myself, push them myself, and approve with a /promote --human-text line; promote checks that GitHub shows me as the pusher
of the approved head and the last editor of the title and body, and
adds nothing to the body. Or I open the upstream PR myself. The one
exception is a thread where I summon the bot (an @-mention with an
ask, or a review request): that is my consent for it to answer there
itself, as a reply or a COMMENT review, never as a tracker question
or a gist for me to post.
tests/upstream-policy.sh tests the check offline.
Open decision: a project whose policy files say nothing about AI is
recorded as human-text for now, to be safe. If that turns out too
strict, the policy-check prompt is where to change it.
The bot never adds Signed-off-by itself. When I approve a fork PR (or
comment /promote) for a project that requires DCO (bootc-dev, among
others), that approval is my sign-off: bot-pr promote adds my
Signed-off-by to the bot's commits, with me as committer, before opening
the upstream PR, and names the approval in its body. Others' commits get it
only with --include-others. A project requires DCO if its branch rules
require the DCO check or if the DCO app's check runs there anyway. For an
upstream PR promote opened without my sign-off, bot-pr signoff <upstream-pr-url> adds it on the same approval, as long as the PR's head
is still what promote opened it with.
Upstream PRs that didn't go through promote still sit with a failing DCO
check until a human signs off. bin/dco-signoff, run by me with my own git
identity and gh login, finds them and fixes that:
dco-signoff --dry-run # list them and prepare the rewrite, push nothing
dco-signoff # ask before pushing each one
dco-signoff https://github.com/bootc-dev/bootc/pull/2494 --yes
It looks at the open PRs authored by cgwalters-bot and those from
cgwalters-forge branches, keeps those whose base branch requires a DCO
status check, and for each one missing my sign-off rebases its commits
with git rebase --signoff onto their existing merge-base (hooks off),
checks that every rewritten commit has the original tree and author,
and force-pushes with a lease on the head it fetched. It refuses to run
as the bot, and to sign off other people's commits without
--include-others; tests/dco-signoff.sh tests it offline.
To pick up changes from the upstream repository:
git fetch upstream && git merge upstream/main
I keep basic config files like ~/.emacs and ~/.bashrc in git.
There might be something useful in there for you. I also have a
collection of miscellaneous scripts in bin/, mostly git wrappers.
If you like this repository, you might also find my personal Ansible https://github.com/cgwalters/ansible-personal repository useful.
This repository also contains AGENTS.md, my default system prompt for agents.
The rootless Praxis credential broker Quadlets are tracked in
dotfiles/.config/containers/systemd/praxis-credential-broker/. The normal
install-dotfiles.sh deployment places them under
~/.config/containers/systemd/. Each pod and container unit is gated with
ConditionHost=xenon, so the user manager will not start it on another host.
The Praxis checkout must remain at
~/src/github/cgwalters-bot/praxis-credential-broker: the gateway unit mounts
%h/src/github/cgwalters-bot/praxis-credential-broker/praxis.yaml, which
systemd expands to the user manager's home directory. Podman secrets and the
persisted authentication volume are deliberately not tracked here.
Do not install this alongside an older manually managed Quadlet directory or
symlink. Replace that deployment only in a planned migration, then run
systemctl --user daemon-reload; this prevents duplicate unit sources and does
not require putting credentials in this repository.