Six versions in two days. That is the pace we shipped this week -- from v2.1.31 to v2.1.36 -- and most of them went out without a single manual version decision. The reason is a new automated versioning layer that reads your commit messages and decides what the next version number should be. But the story behind those six versions is more interesting than the tooling itself, because one of them fixes a bug that made the [Wait Quietly] button on status cards completely useless.
This post covers both: the pipeline that makes rapid releases possible, and the root cause investigation that finally killed the infinite card loop.
The problem with manual version numbers
Every CodePulse release used to start with a conversation. What version should this be? Is it a patch or a minor? Did we remember to update all six version files? The release pipeline handles the build, signing, and deployment automatically -- but the version decision was still manual.
Manual version decisions create two problems. First, they slow things down. A bug fix that takes ten minutes to write takes another five minutes to version, bump six files, commit, tag, and push. Second, they introduce human error. Forget to update Cargo.toml and the launcher shows the wrong version in the tray tooltip. Forget to update constants.ts and the Diagnostics tab reports stale data.
The solution is obvious: let the commit messages decide.
How automated versioning works
CodePulse v2.1.31 introduced semantic-release with conventional commits. The rules are simple.
A commit message starting with fix: triggers a patch release. A commit starting with feat: triggers a minor release. A commit starting with feat!: triggers a major release. Everything else -- chore:, docs:, refactor:, test: -- produces no release at all.
The developer writes the commit message. The pipeline does everything else.
The full automated sequence
When you push to dev, a GitHub Actions workflow called versioning.yml runs. It checks out the repository, installs dependencies, and executes semantic-release. The tool reads every commit since the last version tag, determines the highest-priority bump type, and then:
- Bumps all six version files (package.json, constants.ts, launcher package.json, tauri.conf.json, Cargo.toml, NSIS installer script)
- Generates a CHANGELOG.md entry from the commit messages
- Commits with
chore(release): X.Y.Z [skip ci] - Creates and pushes a git tag
vX.Y.Z - Triggers the release pipeline via workflow dispatch
The release pipeline then builds the Windows installer, signs it with an ed25519 key, uploads it to Cloudflare R2, deploys the download Worker, updates the auto-updater manifest, and creates a GitHub Release. From commit to downloadable installer in about twenty minutes -- no human in the loop.
Why workflow dispatch instead of tag events
The first version of the pipeline relied on GitHub's push tags event to trigger the release workflow. When semantic-release created a tag, the tag push was supposed to trigger release.yml. It did not.
After three failed attempts and significant investigation, we discovered the root cause. Semantic-release pushes commits and tags together using git push --follow-tags. GitHub does not reliably fire a separate push tags event for tags that arrive as part of a --follow-tags push. The tag exists on the remote, but the downstream workflow never sees the event.
The fix was to bypass tag events entirely. The versioning workflow now triggers the release workflow directly using GitHub's workflow_dispatch API. This is a guaranteed delivery mechanism -- no event deduplication, no race conditions, no silent drops. The versioning step captures the version number from semantic-release's output and passes it as an input to the release workflow.
There is one architectural requirement: GitHub only recognizes workflow_dispatch triggers defined on the repository's default branch. Since CodePulse develops on dev but the default branch is main, the workflow files must exist on both branches. A one-line sync command handles this, and the versioning SOP documents the procedure.
The Wait Quietly bug: an infinite loop hiding in plain sight
While the versioning pipeline was shipping, a user report revealed a critical bug in the [Wait Quietly] button on status cards. The button was supposed to put Claude into a quiet waiting state. Instead, it created an infinite loop of cards.
How status cards work
When Claude finishes a task and outputs a status message, the Stop hook fires. CodePulse's classifier examines the message and decides: is Claude asking a question (hold), providing a status update (pass), or answering something routine (auto)?
For pass messages, CodePulse sends a status card to Telegram with three buttons: [Reply], [Wait Quietly], and [Stop]. The card holds the HTTP connection open for up to four minutes, giving the user time to read and respond.
The [Wait Quietly] button was added in v2.1.29 to solve a common annoyance. When Claude finishes and you do not need to reply, the card times out and Claude stops. But if you want Claude to stay alive without replying -- perhaps you are in a meeting, or you want to send instructions later -- there was no good option. [Reply] required typing something. [Stop] killed the session. [Wait Quietly] was supposed to redirect Claude to the wait_for_instructions MCP tool, keeping the session alive silently.
The root cause
The implementation sent a Stop hook response of { decision: "block", reason: "Call the wait_for_instructions tool to wait for user instructions from Telegram." } back to Claude Code. The block decision prevents Claude from stopping. The reason is injected into Claude's conversation as a user-turn message.
Here is what happened next, confirmed through Claude Code GitHub issues and testing:
- Claude receives the block reason as a new prompt
- Claude generates a response -- typically "Understood, I'll wait for your instructions"
- That response triggers another Stop hook
- The classifier sees a new
passmessage - CodePulse sends another status card to Telegram
- The user taps [Wait Quietly] again
- Go to step 1
The logs showed it clearly: three pass classifications at 19:31:18, 19:31:35, and 19:31:49. Each one produced a new card. Each card had a [Wait Quietly] button. Tapping it just restarted the cycle.
The fundamental issue is that there is no way to force Claude to call a specific MCP tool. The reason field is persuasion, not a command. Claude might comply and call wait_for_instructions, or it might just acknowledge the instruction and generate text output -- which triggers another Stop hook. The ralph-wiggum project solved a similar problem with iteration counters and completion promises. CodePulse needed a different approach because the goal is not to loop Claude but to silence it.
The fix: suppression window
The solution is a 60-second rolling suppression window. When the user taps [Wait Quietly]:
- The bridge activates suppression:
waitQuietlyUntil = now + 60 seconds - The current stop is resolved with the block reason as before
- When the next Stop hook arrives (because Claude generated a response), the bridge checks: is suppression active?
- If yes: auto-resolve with
blockand the same reason, without sending a Telegram card - Extend the window another 60 seconds (rolling)
- Repeat silently until Claude finally calls
wait_for_instructionsor the limit is reached
This gives Claude multiple silent chances to comply with the instruction. No cards appear. No buttons to tap. The user sees the single "Waiting quietly" confirmation and nothing else -- which is exactly what they asked for.
Three reset conditions ensure the suppression does not last forever:
- User sends a message. They want to interact -- suppression ends, normal card flow resumes.
- Claude calls
wait_for_instructions. Goal achieved -- the MCP tool blocks Claude and no more Stop hooks fire. - Five silent blocks reached. Safety valve -- if Claude persistently refuses to call the tool, a card reappears so the user can choose [Reply] or [Stop].
The fix ships in v2.1.36 alongside the pipeline improvements. Two files changed: approval-bridge-server.ts for the suppression state and logic, and message-router.ts for the activation and reset points.
Why this matters for remote workflows
The [Wait Quietly] button exists for a specific scenario: you are away from your desk, Claude finishes a task, and you see the notification but do not want to respond yet. Maybe you are reviewing the work mentally. Maybe you are waiting for a colleague's input before giving the next instruction. Maybe you are simply in a meeting.
Before this fix, the only reliable option was to let the card time out and accept that Claude would stop. Then you had to start a new session later. With the suppression window, the session survives. You tap [Wait Quietly] once, put your phone away, and come back to a live session whenever you are ready. This is the workflow the Telegram bridge was designed for -- managing Claude Code from anywhere, on your schedule, not the tool's schedule.
The approval pipeline already learns when to auto-approve recurring tool calls. The Genius Supervisor already answers routine questions without waking you up. The [Wait Quietly] fix completes the picture: when Claude has nothing to ask and you have nothing to say, both sides stay quiet until someone has something meaningful to communicate.
Six versions in two days
Here is what the automated pipeline produced this week:
| Version | What shipped | How it released |
|---|---|---|
| v2.1.31 | Automated versioning layer (semantic-release + commitlint) | Manual (last manual release) |
| v2.1.32 | No user-facing changes (versioning self-test) | Automated versioning, no release build |
| v2.1.33 | Wait Quietly card loop fix | Automated versioning, manual release trigger |
| v2.1.34 | Pipeline fix: tag push approach | Automated versioning, no release build |
| v2.1.35 | Pipeline fix: workflow_dispatch approach | Automated versioning, no release build |
| v2.1.36 | Pipeline fix: GITHUB_TOKEN + actions:write | Automated versioning, automated release |
The progression tells the story. v2.1.31 was the last manual release -- the one that installed the automation. v2.1.32 through v2.1.35 were the pipeline fixing itself, each version created by the automation but with release triggers that did not fire (the bug we fixed). v2.1.36 is the first version where everything worked end-to-end: commit, push, version, tag, trigger, build, sign, deploy.
Every version after v2.1.36 will follow the same path automatically.
The pipeline also enforces commit message discipline through commitlint. A pre-commit hook validates every commit message against the conventional commits specification before it reaches the repository. Malformed messages are rejected instantly -- no ambiguity about whether a change is a fix or a feat, no chance of a typo slipping through and producing an unintended version bump. The developer makes the intent explicit, and the pipeline executes it faithfully.
This is the same philosophy behind the local-first architecture: remove human decision points that can go wrong, and let deterministic systems handle them. Configuration lives in one .env file. Hooks are idempotent. Version numbers follow commit messages. Each layer is simple enough to debug when something goes wrong, and reliable enough that it rarely does.
What this means for CodePulse users
Two things change immediately.
First, updates arrive faster. A bug fix committed at 10 AM is a downloadable installer by 10:20 AM. No version discussions, no manual file edits, no forgotten version bumps. The auto-updater picks up the new version and installs it silently. The gap between "bug found" and "fix on your machine" shrinks from days to minutes.
Second, the [Wait Quietly] button actually works now. When Claude finishes a task and you are not ready to respond, tap [Wait Quietly] and walk away. No more card spam. No more tapping the same button every fifteen seconds. The session stays alive, Claude stays quiet, and your next Telegram message wakes everything up exactly where you left off.
These improvements build on the foundation we have been laying since the initial release: a system that lets you manage Claude Code from your phone without friction. The approval pipeline learns your patterns. The Genius Supervisor answers routine questions. The auto-updater keeps everything current. And now the release pipeline ensures that every improvement reaches you as fast as we can write it.
Download CodePulse to get the latest version with the automated pipeline and Wait Quietly fix, or check the full feature set to see everything CodePulse offers. Already running CodePulse? The auto-updater will deliver v2.1.36 automatically -- just right-click the tray icon and check for updates.