Describe the bug
The in-app update check fails with a generic, non-actionable dialog:
Update Check Failed
Unable to check for updates. Please try again later.
Error: Invalid HTTP response from GitHub.
Two things are going on, and I want to separate the definite bug from the likely trigger.
1. The real bug (independent of what triggered it): SimpleUpdater collapses every failure mode into one opaque message.
Any non-success from GitHub — HTTP 403 (rate limit), a network error, or a response-parsing failure — surfaces as the same "Invalid HTTP response from GitHub." string. The user can't tell a transient rate-limit from a real outage, so "try again later" is the only guidance possible. This is actionable regardless of root cause.
2. Likely trigger: the unauthenticated api.github.com rate limit (60 requests/hour per IP).
A release check almost certainly calls api.github.com/repos/altic-dev/FluidVoice/releases/latest unauthenticated. Unauthenticated requests share a per-IP pool of 60/hour; when it's exhausted GitHub returns HTTP 403 with x-ratelimit-remaining: 0. On a developer machine that already talks to the GitHub API for other reasons, that pool is easy to exhaust, which fits the transient "try again later" symptom.
Caveat, in the interest of honesty: I can't confirm the pool was exhausted at the exact moment of the failure. I reproduced a 403 with an exhausted pool from the same machine/IP, but my own debugging requests consumed some of that quota, so I'm presenting rate-limiting as the most likely trigger, not a proven one. The parseGitHubDate / date-formatter code paths mean a response-parsing failure is a theoretical alternative — but the transient "try again later" wording points away from a deterministic parse bug. Either way, item (1) stands.
Reproduction steps
- Run FluidVoice on a machine whose IP has exhausted the unauthenticated GitHub API limit (e.g. a dev machine that hits
api.github.com), or otherwise force a 403 from api.github.com.
- Trigger the update check (automatic on launch, or via the menu).
- Observe the failure dialog.
To confirm the rate-limit mechanism directly:
curl -sD - -o /dev/null "https://api.github.com/repos/altic-dev/FluidVoice/releases/latest"
# When limited: HTTP/2 403, x-ratelimit-remaining: 0, body "API rate limit exceeded for <ip>"
Expected behavior
- Distinguish failure modes and message accordingly. For a 403 with
x-ratelimit-* headers, say something like "GitHub rate limit reached, retrying after HH:MM" using x-ratelimit-reset, rather than a generic HTTP error.
- Reduce the odds of hitting the limit at all:
- Send
If-None-Match (ETag) conditional requests — a 304 Not Modified does not count against the unauthenticated limit.
- Cache the last successful check and back off; avoid re-checking on every launch/foreground.
- Respect
x-ratelimit-reset for the retry schedule.
Actual behavior
Every update-check failure shows the same "Invalid HTTP response from GitHub." dialog with no way to distinguish a transient rate-limit from a genuine problem, and (if the rate-limit hypothesis holds) no backoff or conditional-request handling to avoid re-hitting the limit.
App Version
1.6.5 (build 16) — note this is already the latest published release (v1.6.5), so a successful check would have reported "already up to date"; instead it errored.
macOS Version
macOS 26.2
Architecture
Apple Silicon
Logs or crash report
No crash. The only surfaced output is the dialog text quoted above ("Error: Invalid HTTP response from GitHub."). Relevant symbols in the binary: SimpleUpdater.checkForUpdate(owner:repo:includePrerelease:), parseGitHubDate, githubDateFormatter, githubFractionalDateFormatter.
Posted on behalf of @schickling
| field |
value |
agent_name |
🧱 cl1-mesa |
agent_session_id |
a75f30d2-971a-4852-be62-587096f0d663 |
agent_tool |
Claude Code |
agent_tool_version |
2.1.202 |
agent_runtime |
Claude Code 2.1.202 |
agent_model |
claude-opus-4-8 |
runtime_profile |
/nix/store/34v4ljmkjh4sgzax68wwgggmzf69zmfk-coding-agent-runtime-profile/share/coding-agents/profile.json |
skills_manifest |
/nix/store/92pwq9mx1byj8nl9an05dyh4baykvf1d-agent-skills-corpus/share/agent-skills/manifest.json |
worktree |
schickling |
machine |
mbp2025 |
tooling_profile |
dotfiles@unknown |
Describe the bug
The in-app update check fails with a generic, non-actionable dialog:
Two things are going on, and I want to separate the definite bug from the likely trigger.
1. The real bug (independent of what triggered it):
SimpleUpdatercollapses every failure mode into one opaque message.Any non-success from GitHub — HTTP 403 (rate limit), a network error, or a response-parsing failure — surfaces as the same
"Invalid HTTP response from GitHub."string. The user can't tell a transient rate-limit from a real outage, so "try again later" is the only guidance possible. This is actionable regardless of root cause.2. Likely trigger: the unauthenticated
api.github.comrate limit (60 requests/hour per IP).A release check almost certainly calls
api.github.com/repos/altic-dev/FluidVoice/releases/latestunauthenticated. Unauthenticated requests share a per-IP pool of 60/hour; when it's exhausted GitHub returns HTTP 403 withx-ratelimit-remaining: 0. On a developer machine that already talks to the GitHub API for other reasons, that pool is easy to exhaust, which fits the transient "try again later" symptom.Caveat, in the interest of honesty: I can't confirm the pool was exhausted at the exact moment of the failure. I reproduced a 403 with an exhausted pool from the same machine/IP, but my own debugging requests consumed some of that quota, so I'm presenting rate-limiting as the most likely trigger, not a proven one. The
parseGitHubDate/ date-formatter code paths mean a response-parsing failure is a theoretical alternative — but the transient "try again later" wording points away from a deterministic parse bug. Either way, item (1) stands.Reproduction steps
api.github.com), or otherwise force a 403 fromapi.github.com.To confirm the rate-limit mechanism directly:
Expected behavior
x-ratelimit-*headers, say something like "GitHub rate limit reached, retrying after HH:MM" usingx-ratelimit-reset, rather than a generic HTTP error.If-None-Match(ETag) conditional requests — a304 Not Modifieddoes not count against the unauthenticated limit.x-ratelimit-resetfor the retry schedule.Actual behavior
Every update-check failure shows the same "Invalid HTTP response from GitHub." dialog with no way to distinguish a transient rate-limit from a genuine problem, and (if the rate-limit hypothesis holds) no backoff or conditional-request handling to avoid re-hitting the limit.
App Version
1.6.5 (build 16) — note this is already the latest published release (
v1.6.5), so a successful check would have reported "already up to date"; instead it errored.macOS Version
macOS 26.2
Architecture
Apple Silicon
Logs or crash report
No crash. The only surfaced output is the dialog text quoted above ("Error: Invalid HTTP response from GitHub."). Relevant symbols in the binary:
SimpleUpdater.checkForUpdate(owner:repo:includePrerelease:),parseGitHubDate,githubDateFormatter,githubFractionalDateFormatter.Posted on behalf of @schickling
agent_nameagent_session_idagent_toolagent_tool_versionagent_runtimeagent_modelruntime_profileskills_manifestworktreemachinetooling_profile