Skip to content

fix: Implement speed test and health check for Cap desktop app - #4204

Open
ondrej-sedlak wants to merge 2 commits into
CapSoftware:mainfrom
ondrej-sedlak:bounty-73-implement-speed-test
Open

ondrej-sedlak wants to merge 2 commits into
CapSoftware:mainfrom
ondrej-sedlak:bounty-73-implement-speed-test

Conversation

@ondrej-sedlak

@ondrej-sedlak ondrej-sedlak commented Nov 13, 2026 •

Copy link
Copy Markdown

Description

  • new network_status module runs a startup health check that uploads a
    probe payload to /api/desktop/health-check and flags failures in the UI
  • periodic upload speed test (skipped while a recording is active) drives
    a bottom-left indicator and caps studio recording quality by bandwidth
  • new authenticated /api/desktop/health-check endpoint in the web app

Resolves #73
Issue: #73
/claim #73

RetriggerView in GreptileConfidence Score: 3/5

Do not merge until the desktop build error and startup panic are fixed and the repository's API and comments rules are satisfied.

Findings

  1. P1 Desktop build fails ▶
  2. P1 Startup check panics ▶
  3. P2 Slow links skip quality cap ▶
  4. P2 Recovered connections still show failure ▶
  5. P2 Probes compete with real uploads ▶
  6. P2 Protected deployments reject probes ▶
  7. P2 Security Body limit checks too late ▶
  8. P2 New route breaks API rule ▶
  9. P2 Comments repeat the code ▶
  10. P2 Network labels ignore language ▶
Fix with agent prompt
### Issue 1
apps/desktop-gpui/src/network_status.rs:195-196
`recording_active` calls `.unwrap_or(false)` on a `bool`. `AsyncApp::update` returns the closure's value directly, unlike `AsyncWindowContext::update`. Remove `.unwrap_or(false)` so the desktop app builds.

```suggestion
    cx.update(|cx| RecordingSession::global(cx).read(cx).phase != Phase::Idle)
```

### Issue 2
apps/desktop-gpui/src/network_status.rs:204-205
`spawn_monitor` runs `post_probe` on GPUI's main-thread task, but `post_probe` creates a Tokio timer without entering the Tokio runtime. This panics when the startup check starts, before authentication is checked. Run the request and its timeout through `gpui_tokio::Tokio::spawn`, then publish the result on GPUI.

### Issue 3
apps/desktop-gpui/src/network_status.rs:179-181
A connection below roughly 0.84 Mbps cannot upload the 2 MiB probe within 20 seconds. That timeout sets `upload_mbps` to `None`, and `effective_studio_quality` keeps the configured quality. The slowest connections therefore get no cap. Distinguish a timed-out measurement from an unavailable one, or use a smaller or adaptive probe.

### Issue 4
apps/desktop-gpui/src/network_status.rs:184-187
A brief network failure at launch leaves the red upload warning visible until the app restarts. `run_health_check` writes `health` only once, and later successful speed tests never clear it. Update `health` after subsequent probes, or retry a failed health check.

### Issue 5
apps/desktop-gpui/src/network_status.rs:208-211
The idle check only runs before the speed test starts. If the user starts an Instant recording during the probe, its 2 MiB upload continues for up to 20 seconds beside the recording's upload. Idle also does not mean that earlier uploads have finished. Cancel probes when recording starts and avoid starting them while real uploads are active.

### Issue 6
apps/desktop-gpui/src/auth.rs:454-458
`authed_raw_request` skips `apply_desktop_headers`, unlike the existing authenticated request helper. If `VITE_VERCEL_AUTOMATION_BYPASS_SECRET` is configured for a protected deployment, probes omit its bypass header and can be rejected before reaching the endpoint. Apply the shared headers to raw requests too.

```suggestion
    let request = apply_desktop_headers(client.request(method, url))
        .header("Content-Type", content_type)
        .body(body)
        .bearer_auth(token);
```

### Issue 7
apps/web/app/api/desktop/[...route]/health-check.ts:11-12
The 8 MiB limit runs after `arrayBuffer()` has loaded the entire request into memory. An authenticated caller can make the server allocate far more than the intended limit before it returns 413, especially on self-hosted deployments. Count bytes while reading and stop once the limit is reached.

**How this was verified:** The authenticated POST handler reads the complete caller-supplied body before checking its size.

### Issue 8
apps/web/app/api/desktop/[...route]/health-check.ts:4
The new route uses Hono handlers, but `AGENTS.md` requires new routes under `apps/web/app/api/*` to use the `HttpApi` class/group/endpoint pattern and `apiToHandler`. The explicit Hono exception covers only the developer v1 route families, not `/api/desktop`. Implement this endpoint with the required pattern before merging.

### Issue 9
apps/desktop-gpui/src/network_status.rs:99
The comment above `quality_for_upload_speed` only repeats the function's name and return value. `AGENTS.md` prohibits comments that narrate code or restate types. The same pattern appears above `effective_studio_quality` and in the module introduction. Remove these comments or explain a non-obvious decision to satisfy the requirement before merging.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 10
apps/desktop-gpui/src/main_window.rs:3982-3986
The new status labels are hardcoded in English while the rest of this window uses `tr` for translated text. Add translation keys for the warning, checking message, and speed label so changing the app's language also changes this new UI.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds authenticated upload probes, a network indicator, and bandwidth-based Studio quality limits.

  • Cap desktop checks uploads and shows network status.
  • Studio recordings now stay within the measured upload speed.

Reviews (1) · Last reviewed commit: "feat: add upload speed test and startup ..." · Reviewed by Greptile

- new network_status module runs a startup health check that uploads a
  probe payload to /api/desktop/health-check and flags failures in the UI
- periodic upload speed test (skipped while a recording is active) drives
  a bottom-left indicator and caps studio recording quality by bandwidth
- new authenticated /api/desktop/health-check endpoint in the web app
@vercel

vercel Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
cap-status Skipped Skipped Nov 13, 2026 1:10pm UTC

@vercel
vercel Bot temporarily deployed to Preview – cap-status November 13, 2026 12:57 Inactive
@superagent-security

Copy link
Copy Markdown

🚨 Contributor flagged. Click here for more info: Superagent Dashboard

Comment thread apps/desktop-gpui/src/network_status.rs Resolved
Comment thread apps/desktop-gpui/src/network_status.rs Resolved
Comment thread apps/desktop-gpui/src/network_status.rs Outdated
Comment on lines +179 to +181
Err(error) => {
tracing::warn!(error = %error, "upload speed test failed");
None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Slow links skip quality cap

A connection below roughly 0.84 Mbps cannot upload the 2 MiB probe within 20 seconds. That timeout sets upload_mbps to None, and effective_studio_quality keeps the configured quality. The slowest connections therefore get no cap. Distinguish a timed-out measurement from an unavailable one, or use a smaller or adaptive probe.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/network_status.rs
Line: 179-181

Comment:
**Slow links skip quality cap**

A connection below roughly 0.84 Mbps cannot upload the 2 MiB probe within 20 seconds. That timeout sets `upload_mbps` to `None`, and `effective_studio_quality` keeps the configured quality. The slowest connections therefore get no cap. Distinguish a timed-out measurement from an unavailable one, or use a smaller or adaptive probe.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread apps/desktop-gpui/src/network_status.rs Outdated
Comment on lines +184 to +187
let _ = cx.update(|cx| {
status.update(cx, |status, cx| {
status.upload_mbps = outcome;
status.last_speed_test = Some(Instant::now());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Recovered connections still show failure

A brief network failure at launch leaves the red upload warning visible until the app restarts. run_health_check writes health only once, and later successful speed tests never clear it. Update health after subsequent probes, or retry a failed health check.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/network_status.rs
Line: 184-187

Comment:
**Recovered connections still show failure**

A brief network failure at launch leaves the red upload warning visible until the app restarts. `run_health_check` writes `health` only once, and later successful speed tests never clear it. Update `health` after subsequent probes, or retry a failed health check.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread apps/desktop-gpui/src/network_status.rs Resolved
Comment on lines +454 to +458
let request = client
.request(method, url)
.header("Content-Type", content_type)
.body(body)
.bearer_auth(token);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Protected deployments reject probes

authed_raw_request skips apply_desktop_headers, unlike the existing authenticated request helper. If VITE_VERCEL_AUTOMATION_BYPASS_SECRET is configured for a protected deployment, probes omit its bypass header and can be rejected before reaching the endpoint. Apply the shared headers to raw requests too.

Suggested change
let request = client
.request(method, url)
.header("Content-Type", content_type)
.body(body)
.bearer_auth(token);
let request = apply_desktop_headers(client.request(method, url))
.header("Content-Type", content_type)
.body(body)
.bearer_auth(token);
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/auth.rs
Line: 454-458

Comment:
**Protected deployments reject probes**

`authed_raw_request` skips `apply_desktop_headers`, unlike the existing authenticated request helper. If `VITE_VERCEL_AUTOMATION_BYPASS_SECRET` is configured for a protected deployment, probes omit its bypass header and can be rejected before reaching the endpoint. Apply the shared headers to raw requests too.

```suggestion
    let request = apply_desktop_headers(client.request(method, url))
        .header("Content-Type", content_type)
        .body(body)
        .bearer_auth(token);
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +11 to +12
const body = await c.req.arrayBuffer();
if (body.byteLength > MAX_PROBE_BYTES) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security Body limit checks too late

The 8 MiB limit runs after arrayBuffer() has loaded the entire request into memory. An authenticated caller can make the server allocate far more than the intended limit before it returns 413, especially on self-hosted deployments. Count bytes while reading and stop once the limit is reached.

How this was verified: The authenticated POST handler reads the complete caller-supplied body before checking its size.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/app/api/desktop/[...route]/health-check.ts
Line: 11-12

Comment:
**Body limit checks too late**

The 8 MiB limit runs after `arrayBuffer()` has loaded the entire request into memory. An authenticated caller can make the server allocate far more than the intended limit before it returns 413, especially on self-hosted deployments. Count bytes while reading and stop once the limit is reached.

**How this was verified:** The authenticated POST handler reads the complete caller-supplied body before checking its size.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

import { Hono } from "hono";
import { withAuth } from "../../utils";

export const app = new Hono();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 New route breaks API rule

The new route uses Hono handlers, but AGENTS.md requires new routes under apps/web/app/api/* to use the HttpApi class/group/endpoint pattern and apiToHandler. The explicit Hono exception covers only the developer v1 route families, not /api/desktop. Implement this endpoint with the required pattern before merging.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/app/api/desktop/[...route]/health-check.ts
Line: 4

Comment:
**New route breaks API rule**

The new route uses Hono handlers, but `AGENTS.md` requires new routes under `apps/web/app/api/*` to use the `HttpApi` class/group/endpoint pattern and `apiToHandler`. The explicit Hono exception covers only the developer v1 route families, not `/api/desktop`. Implement this endpoint with the required pattern before merging.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

}
}

/// The quality tier the measured upload speed can sustain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Comments repeat the code

The comment above quality_for_upload_speed only repeats the function's name and return value. AGENTS.md prohibits comments that narrate code or restate types. The same pattern appears above effective_studio_quality and in the module introduction. Remove these comments or explain a non-obvious decision to satisfy the requirement before merging.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/network_status.rs
Line: 99

Comment:
**Comments repeat the code**

The comment above `quality_for_upload_speed` only repeats the function's name and return value. `AGENTS.md` prohibits comments that narrate code or restate types. The same pattern appears above `effective_studio_quality` and in the module introduction. Remove these comments or explain a non-obvious decision to satisfy the requirement before merging.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +3982 to +3986
"Upload check failed - contact support".to_string()
} else {
match status.upload_mbps {
Some(mbps) => format!("{mbps:.1} Mbps up"),
None => "Checking upload...".to_string(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Network labels ignore language

The new status labels are hardcoded in English while the rest of this window uses tr for translated text. Add translation keys for the warning, checking message, and speed label so changing the app's language also changes this new UI.

Knowledge Base Used: GPUI desktop client

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/main_window.rs
Line: 3982-3986

Comment:
**Network labels ignore language**

The new status labels are hardcoded in English while the rest of this window uses `tr` for translated text. Add translation keys for the warning, checking message, and speed label so changing the app's language also changes this new UI.

**Knowledge Base Used:** [GPUI desktop client](https://app.greptile.com/-/switch-tenant?tenant=cap&next=%2Fcustom-context%2Fknowledge-base%2Fcapsoftware%2Fcap%2F-%2Fdocs%2Fgpui-desktop-client.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@vercel
vercel Bot temporarily deployed to Preview – cap-status November 13, 2026 13:10 Inactive

This branch was previously deployed

1 inactive deployment
Preview – cap-status — 471cc938 Deployed Nov 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement speed test and health check for Cap desktop app

1 participant