Yep, this does what the title says. StandupBot reads the actual PR diff and commit messages, runs them through any OpenAI-compatible LLM endpoint, and writes a structured Summary / Changes / Testing description — so you never have to write "fixed stuff" again.
I built this for my own team because I was tired of staring at empty PR boxes after every fix. Then I put it on GitHub Marketplace so anyone can use it. MIT licensed, no strings.
A GitHub Action that writes your PR descriptions for you. It runs the real diff and commits through an LLM you choose, and fills in a structured Summary / Changes / Testing description plus up to three labels — so you stop hand-writing PR bodies and standup updates.
Bring your own model: it talks to any OpenAI-compatible endpoint (OpenAI, OpenRouter, Ollama, LM Studio, …). No provider, URL, or model is hardcoded.
Generated end-to-end by StandupBot for a real PR (nodejs/node#64573) from its actual diff, using a real LLM endpoint. This is the description it produced verbatim:
Summary #
Add
lchownSync
to the VFS implementation so that symbolic link ownership can be changed without following the link, matching the behavior offs.lchownSync
.## Changes
- doc/api/vfs.md: added
lchownSync(path, uid, gid)
to the list of VFS API signatures.- lib/internal/vfs/file_system.js: added synchronous
lchownSync
method toVirtualFileSystem
and updated the async…
You open a PR. Forty files changed. Two hours of focused work. The description field is empty.
So you write: fixed stuff
. Or updated code
. Or changes requested by reviewer
.
Three months later, someone (you) is running git log -p
trying to figure out why the config format changed in April. The PR description was supposed to save that investigation. Nobody wrote it.
We tried before:
| Approach | Why it failed |
|---|---|
| PR templates | Everyone types "see title" into the template |
| Checklist bots | Nagging doesn't scale; people ignore them |
| Reviewer enforcement | Becomes the team's most hated job |
The gap isn't discipline. It's that writing PR descriptions is unrewarded work. You get the same merge button whether you write a detailed description or "fixed stuff."
So I built StandupBot.
This is verbatim output from nodejs/node#64573, generated from the actual diff:
Summary #
Add
lchownSync
to the VFS implementation so that symbolic link ownership can be changed without following the link, matching the behavior offs.lchownSync
.## Changes
- doc/api/vfs.md: added
lchownSync(path, uid, gid)
to VFS API signatures.- lib/internal/vfs/file_system.js: added synchronous
lchownSync
method and updated the async wrapper.- lib/internal/vfs/provider.js: added default
lchownSync
method with JSDoc comment.- lib/internal/vfs/providers/memory.js: implemented
lchownSync
that updates uid/gid of the link entry.- lib/internal/vfs/setup.js: changed handler to invoke
vfs.lchownSync
instead ofvfs.chownSync
.- test/parallel/test-vfs-lchown-symlink.js: new test verifying sync, callback, and promise variants.
Testing #
The new test exercises
fs.lchownSync
,fs.lchown
(callback), andfsp.lchown
(promise) on symlinks inside a VFS mount, asserting correct uid/gid changes.
Nobody edited that. It went straight from the model into the PR body. The action also returns a normalized title
and labels
clamped to bug | feature | chore | docs | refactor
— so your label taxonomy stays clean.
Add .github/workflows/standupbot.yml
:
name: StandupBot
on:
pull_request:
types: [opened, synchronize]
permissions:
pull-requests: write
contents: read
jobs:
describe:
runs-on: ubuntu-latest
steps:
- uses: XenoCyber0/StandUpBot@v1
with:
llm-base-url: ${{ secrets.LLM_BASE_URL }}
llm-api-key: ${{ secrets.LLM_API_KEY }}
model: ${{ vars.LLM_MODEL }}
Set LLM_API_KEY
as a secret, LLM_MODEL
as a variable. That's the whole thing.
The action talks to any OpenAI-compatible chat API. Nothing is hardcoded. All of these work:
OpenAI → https://api.openai.com/v1
OpenRouter → https://openrouter.ai/api/v1
Ollama → http://localhost:11434/v1
LM Studio → http://localhost:1234/v1
LocalAI → http://localai:8080/v1
You decide.
You pick the model. You control the data path. Switching providers later is one secret update.
If you want your code to never leave your network:
GitHub repo ──webhook──▶ self-hosted Actions runner ──HTTP──▶ Ollama
(on your LAN) (your LLM box)
llm-base-url
at your Ollama/LM Studio instancellm-api-key
can be any non-empty string — Ollama ignores itYour diff never crosses the internet. The only outbound call is writing the PR body back to GitHub, which... GitHub already owns.
Model notes: In my testing, Qwen2.5-Coder 7B and similar 7–9B instruction-tuned models work well. Below ~3B params, file-name hallucinations start appearing.
The rule is simple:
Your hand-written descriptions are safe. If you ever overwrite what it generated, it takes the hint and stays out.
Three parts worth stealing:
1. Diff budget. Fetches the PR diff, applies gitignore-style exclusions from .standupbot.yml
before anything hits a prompt:
exclude:
- package-lock.json
- '**/*.lock'
- '**/dist/**'
The diff is hard-capped at ~24KB — a monster PR can't blow up your LLM context window.
2. Map-reduce for big diffs. Under the budget, one call. Over it? Each file gets summarized individually (max 8 LLM calls), then merged into the final description.
3. Structured output enforcement. The prompt requires a rigid schema — title
, summary
, changes[]
, testing
, labels[]
— and the parser clamps labels to the allowed set. If the model invents urgent-pls
, it gets dropped, not shipped.
Marketplace: github.com/marketplace/actions/standupbot-pr-describer
Source: github.com/XenoCyber0/StandUpBot (MIT)
Issues/feedback: github.com/XenoCyber0/StandUpBot/issues
Written by the person who finally read "fixed stuff" one too many times and decided to do something about it.