The whole integration is one workflow file in your repo, built from
GitHub's own events, the in-cluster runner, and tiny deliver. There is
no app to install and no bot account.
tiny setup, public half on
the repo).Label an issue tiny and this runs — for about five seconds:
# .github/workflows/tiny.yml
name: tiny
on:
issues:
types: [labeled]
schedule:
- cron: "*/5 * * * *" # the outbox courier (below)
permissions:
contents: write
pull-requests: write
issues: write
jobs:
deliver:
if: github.event_name == 'issues' && github.event.label.name == 'tiny'
runs-on: [self-hosted, tiny]
timeout-minutes: 5
env:
N: ${{ github.event.issue.number }}
# Issue title/body are ATTACKER-CONTROLLED: they enter only as env
# vars, never interpolated into the script itself.
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
steps:
- name: deliver the issue to the root session
run: |
{
printf 'GitHub issue #%s of %s: %s\n\n' "$N" "$GITHUB_REPOSITORY" "$ISSUE_TITLE"
printf '%s\n\n' "$ISSUE_BODY"
echo "Handle it end to end. You have NO git credentials; the outbox pushes for you:"
echo "- Code: commit on branch tiny/issue-$N, then: git bundle create /workspace/outbox/tiny-issue-$N.bundle tiny/issue-$N"
echo "- Textual answer: commit REPLY.md on tiny/reply-$N and bundle that branch — it becomes an issue comment."
echo "- Never push or call the GitHub API yourself; the bundle IS the send."
} | tiny deliver root --ensure --repo "https://github.com/$GITHUB_REPOSITORY.git"
The task hand-off is a prompt piped into tiny deliver. --ensure
creates the root session on first contact and --repo seeds its
workspace. The prompt carries your conventions (branch names, how to
send work back), and since it is plain text, editing it is how you
change the process.
Every ~5 minutes a second job empties the outbox: it
runs tiny export to lift pending git bundles out of sessions, rebases
each branch onto main, pushes with the job's own short-lived token,
opens the PR (or posts REPLY.md as an issue comment), and acks the
bundle only after the push succeeded.
export:
if: github.event_name == 'schedule'
runs-on: [self-hosted, tiny]
timeout-minutes: 5
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- run: |
rm -rf outbox && tiny export
for b in outbox/*.bundle; do
# fetch the bundle's branch, rebase onto origin/main,
# push with $GH_TOKEN, open the PR / post the comment,
# then retire the bundle:
tiny export --ack "$(basename "$b")"
done
The push-and-PR plumbing between those lines is ~60 lines of shell; take it verbatim from the reference workflow in seedling — it handles rebase conflicts (fail loud, keep the bundle), the 403 from a missing org toggle (fail without acking, so the bundle retries once you flip it), and reply branches.
Branches pushed with a job token trigger no further workflows (GitHub's recursion guard), so the courier has to finish the job itself: push, open the PR, comment, ack.
A bundle is only retired after its work arrived. For a real end-to-end run, see seedling PR #2, which started as a labeled issue.