I submitted a couple of small fixes to OpenClaw — a Mattermost threading bug (2 lines) and a chatmode mention issue (1 line). The PRs were in review, but I wanted to test them on my deployment now. Maintaining a full fork for 3 lines felt absurd.
The trick
The upstream image ships TypeScript source and uses jiti for runtime transpilation. So you can patch .ts files directly — no build step. And git is already in the image.
Store .patch files in the repo, apply them during the Docker build:
COPY patches/ /tmp/patches/
RUN cd /app && \
for p in /tmp/patches/*.patch; do \
[ -e "$p" ] || continue; \
echo "Applying patch: $(basename $p)"; \
git apply "$p" || { echo "FAILED: $(basename $p)"; exit 1; }; \
done && \
rm -rf /tmp/patches
git apply fails loudly if a patch doesn't apply cleanly — which is what you want.
Generating patches
This is where I initially got tripped up. If you just git diff main...my-branch, you'll get all the upstream changes between the fork point and your branch tip, not just your fix. The branch includes dozens of unrelated upstream commits.
Instead, extract just your commit:
git show <your-commit-sha> -- path/to/file.ts \
> patches/12345-short-description.patch
Then build the image. If git apply fails, the build fails. That's your test.
Naming convention
I name patches <PR-number>-<short-description>.patch. The PR number is key — when bumping the upstream version, you check:
gh pr view 27744 --repo openclaw/openclaw --json state -q '.state'
# MERGED → delete the patch
# OPEN → keep it
Version bumps
When you bump the upstream version, patches may not apply if the context changed. The first time I did this (v2026.2.25 → v2026.2.26), both patches needed line number updates because upstream refactored the file. The surrounding code was identical though — regenerated both in 2 minutes.
The workflow is: bump version, build, fix whatever breaks. If a patch fails, check whether the PR merged (delete the patch) or just regenerate it against the new version.
Why not sed / file overlay / submodule / fork build?
git apply hits a sweet spot that the alternatives don't:
| Approach | Problem |
|---|---|
sed in Dockerfile | Fragile, no context-awareness, unreadable |
| Copy entire patched files | Lose track of what changed; version bumps require full file regeneration |
| Git submodule of fork | Overkill; submodules are painful |
| Clone fork in Dockerfile | Slow, caching issues, complex for tiny changes |
git apply is contextual (fails if surrounding code changed), reviewable (standard diff format), and needs no extra tooling. The failure mode is the right one: a broken build tells you something changed upstream and you need to look.