Engineering · September 15, 2026 · John Jeong
Why we stopped rebuilding the desktop app on every PR
We keep fast checks in the PR loop, batch native verification, and make the release candidate carry the responsibility.

I've been pushing code faster and faster, and CI has started feeling like the bottleneck.
I wrote about the flip side of this on X: cloud agents can take contained backend work end to end, but local UI work still bottlenecks on someone sweeping every PR by hand.
An agent makes a change. A code reviewer finds something. The agent fixes it and pushes again. Rebuilding the desktop app across every platform after each pass adds a lot of work to that loop.
I wanted to keep moving without dropping the checks that make a release trustworthy. So I changed when we run them.
We've been doing this in Anarlog, our open-source desktop meeting app. The native desktop checks run on a schedule and against release candidates. The PR loop keeps the faster checks and tests relevant to the change.
It's a tradeoff I'm making, not a claim that we've found a way to get the same confidence for free.
What stays on every change
Our release audit describes the split: lint, formatting, typechecks, and unit/integration tests per PR; native desktop verification separately.
For the desktop's JavaScript side, the workflow includes:
| 45 | js_ci: |
| 46 | if: ${{ !startsWith(github.head_ref || '', 'blog/') }} |
| 47 | runs-on: ubuntu-24.04 |
| 48 | steps: |
| 49 | - uses: actions/checkout@v4 |
| 50 | with: |
| 51 | submodules: recursive |
| 52 | fetch-depth: 0 |
| 53 | - uses: ./.github/actions/pnpm_install |
| 54 | - run: pnpm -F ui build |
| 55 | - run: pnpm -F desktop typecheck |
| 56 | - run: pnpm -F desktop test |
Other workflows still run Rust checks and tests when the affected subsystem needs them. A CLI change doesn't get a free pass because we moved native desktop verification out of the PR loop.
Finishing feedback on a revision we've already replaced isn't worth much.
The cost here needs some precision. Anarlog is public, and we moved its Linux and macOS runners off paid infrastructure onto GitHub-hosted runners. I'm talking about repeated compute work and waiting, not claiming a measured reduction in our GitHub bill.
What moves out of the PR loop
The desktop workflow has a daily schedule at 09:00 UTC:
The native jobs only run for scheduled or manually dispatched verification. This is the macOS condition:
| 58 | macos_ci: |
| 59 | if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} |
Windows, Linux, and Swift follow the same scheduling distinction. We can dispatch the checks against a release candidate instead of running them after every review fix.
This does let broken code reach main. On September 14, a scheduled run failed with a Rust compilation error. A new provider was missing from a match expression; the fix added the missing arm.
That example makes the tradeoff concrete. We found the error after merging rather than before. The daily job didn't prevent the mistake, and it didn't fix it for us.
6 of the last 20 scheduled runs failed and 5 more were cancelled, so I wouldn't present this as a system that has already proved it preserves reliability.
A merge doesn't replace the user's app
The reason I'm willing to make this tradeoff is that Anarlog is a desktop app. Merging to main doesn't immediately replace the version installed on someone's computer.
We have a separate release decision, and we need to use it properly.
Our Nightly workflow runs desktop verification before building and publishing:
| 18 | verify: |
| 19 | if: github.ref == 'refs/heads/main' |
| 20 | uses: ./.github/workflows/desktop_ci.yaml |
| 21 | |
| 22 | build-and-publish: |
| 23 | needs: verify |
Stable publication checks the exact candidate commit, a successful signed dry run, and artifact provenance. But some of the broader release audit still depends on the release owner following the process. The publish workflow doesn't mechanically enforce every candidate check or prove that we used the app.
That last part matters. Compiling Rust catches compilation errors. It doesn't prove that the app won't panic or get stuck during an actual meeting.
Our release instructions say to pin a Nightly candidate for two to three working days and record real meeting results. Check recording, transcription, and sync. Restart the app to confirm notes persist.
CI or elapsed time alone is not evidence of use.
Leaving a candidate around for three days isn't testing it.
Faster changes need a better record
There's another problem with moving faster: remembering why something changed.
A diff shows the implementation. A commit message doesn't always preserve the original request or the decision that led to it. If a problem surfaces later, I want that context available without having to reconstruct the conversation.
I added repository-local workflow skills for both Anarlog and Char. The Anarlog skill includes:
| 24 | ## Record everything |
| 25 | |
| 26 | - Work lives on a Linear issue. If the user states a decision, files a bug, or scopes new work, write it to Linear in the same turn. |
| 27 | - Progress, decisions, blockers, and handoffs go on that issue as comments. |
| 28 | - Durable specs, research, and product context go in Linear documents on the team or project. |
| 29 | - Link the issue in commits/PRs when one exists (`ANLG-123`). |
This is a skill the agent is instructed to follow, not a hook that automatically archives every message. Explicit brainstorming stays discussion-first. The goal is to preserve decisions and working context alongside implementation.
Char uses the same approach to recording work, although its CI setup is different. I'm using Anarlog here because the workflows and release process are public.
Linear won't tell me whether a build is safe. It gives me somewhere to look for the intention behind a change when I'm trying to understand what went wrong.
The responsibility moves with the checks
Running the full native suite before every merge would catch some failures earlier. We've chosen faster feedback during development and accepted that some native problems will be discovered later.
That makes the release candidate more important. We need to know exactly which commit we're testing, what passed, and what we actually tried in the app. New changes on main can't silently become part of that evidence.
I'm trying to ship faster without making users do the verification we deferred. If we skip that verification at release time too, we've just removed the checks.