# How Worktrunk Handles Network Requests for CI Status and PR Details

> Discover how Worktrunk efficiently fetches CI status and PR details by using background CLI commands, caching results, and updating the UI via file-system events without blocking the main thread.

- Repository: [Maximilian Roos/worktrunk](https://github.com/max-sixty/worktrunk)
- Tags: internals
- Published: 2026-09-14

---

**Worktrunk retrieves CI status and PR details by lazily launching background CLI commands that query GitHub or GitLab, caching results atomically to disk, and updating the UI via file-system events without blocking the main thread.**

Worktrunk (`max-sixty/worktrunk`) is a Rust-based CLI tool for managing Git worktrees that displays real-time CI badges and pull request metadata. To maintain interface responsiveness while fetching remote data, it implements a **cache-first, non-blocking architecture** that delegates network requests to platform-specific CLI tools and hydrates the UI asynchronously.

## The Cache-First Data Flow

When rendering a list of worktrees with CI badges, Worktrunk follows a five-step pipeline that prioritizes local data before hitting the network.

### Step 1: Local Cache Lookup

Before spawning any network requests, Worktrunk checks a **file-based cache** located at `.git/wt/cache/ci-status/<branch>.json`. The cache implementation in [`src/cache.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/cache.rs) attempts to load existing CI data:

```rust
let ci = CiCache::load(branch)?;
if let Some(entry) = ci {
    // Use cached CI info immediately
    render_ci(entry);
}

```

If the cache entry exists, Worktrunk renders the CI status instantly. This lookup occurs in the main rendering path, ensuring the first frame of any command (e.g., `wt list`) displays before any network request completes.

### Step 2: Background Command Execution

When the cache misses, Worktrunk spawns a **background process** to fetch fresh data. In [`src/output/concurrent.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/output/concurrent.rs), the `Cmd` wrapper executes forge-specific CLI tools—`gh` for GitHub or `glab` for GitLab—decoupled from the main thread:

```rust
let cmd = Cmd::new("gh")
    .args(["pr", "list", "--json", "number,title,author,headRefOid"])
    .current_dir(&repo_path)
    .scrub_git_discovery_env()
    .run();

```

The `Cmd::new(...).run()` method captures stdout/stderr while allowing the UI to continue rendering, preventing network latency from freezing the picker interface.

### Step 3: Structured JSON Parsing

Worktrunk requests **machine-readable JSON** via the `--json` flag rather than parsing human-readable text, eliminating locale-dependent fragility. The picker logic in [`src/picker/prs.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/picker/prs.rs) deserializes the output into internal structs:

```rust
let pr_info: Vec<PrInfo> = serde_json::from_slice(&cmd.stdout)?;

```

This structured approach ensures consistent parsing across different GitHub/GitLab versions and user locales.

### Step 4: Atomic Cache Persistence

After parsing, Worktrunk writes the data atomically to prevent corruption during concurrent access. The `write_atomically` function in [`src/utils.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/utils.rs) (lines 149-158) ensures the cache file is written completely or not at all:

```rust
write_atomically(&cache_path, serde_json::to_vec(&pr_info)?)?;

```

The atomic write guarantees that subsequent cache reads never encounter partial JSON, even if the process terminates mid-write.

### Step 5: Event-Driven UI Refresh

Worktrunk monitors the cache directory using the `notify` crate (Linux/macOS) to detect when background writes complete. In [`src/output/picker_preview.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/output/picker_preview.rs), the file system watcher triggers row updates:

```rust
watcher
    .watch(cache_dir, RecursiveMode::NonRecursive)
    .expect("watch CI cache");
for event in watcher.rx.iter() {
    if let EventKind::Modify(_) = event.kind {
        // Re-render the row with fresh CI/PR data
        list.render_row(updated_row);
    }
}

```

This **progressive refresh** strategy updates the CI column after the network request finishes, without requiring the user to restart the command.

## Network Safety Guarantees

Worktrunk implements several safeguards to handle unreliable networks gracefully.

### Non-Blocking UI Architecture

The initial render cycle completes **before** any network request finishes. CI columns appear empty initially and populate as background fetches resolve. This guarantees that slow forge APIs cannot stall the entire `wt` command.

### Timeout Handling

Background processes inherit a global `REMOTE_DETECTION_TIMEOUT` (approximately 30 seconds). Commands exceeding this threshold are terminated, preventing indefinite hangs when GitHub or GitLab experiences outages.

### CLI Abstraction

By delegating network operations to the official `gh` and `glab` CLIs rather than implementing raw HTTP clients, Worktrunk leverages those tools' authentication handling, rate limiting, and retry logic automatically.

## Key Source Files

Understanding the implementation requires examining these specific modules:

- **[`src/cache.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/cache.rs)** – Implements the file-based CI-status cache read/write operations
- **[`src/output/concurrent.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/output/concurrent.rs)** – Runs background forge commands safely using the `Cmd` wrapper
- **[`src/picker/prs.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/picker/prs.rs)** – Manages per-worktree PR fetches and picker UI coordination
- **[`src/utils.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/utils.rs)** – Contains `write_atomically` for safe cache persistence
- **[`tests/integration_tests/config_state.rs`](https://github.com/max-sixty/worktrunk/blob/main/tests/integration_tests/config_state.rs)** – Validates CI cache creation and state management
- **[`tests/integration_tests/switch_picker.rs`](https://github.com/max-sixty/worktrunk/blob/main/tests/integration_tests/switch_picker.rs)** – Demonstrates mocked PR JSON handling and UI refresh
- **[`tests/integration_tests/ci_status.rs`](https://github.com/max-sixty/worktrunk/blob/main/tests/integration_tests/ci_status.rs)** – End-to-end integration tests for the full CI detection pipeline

## Summary

- Worktrunk uses a **cache-first strategy** checking `.git/wt/cache/ci-status/<branch>.json` before making network requests
- Network queries run via **background CLI processes** (`gh` or `glab`) spawned through `Cmd::new(...).run()` in [`src/output/concurrent.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/output/concurrent.rs)
- All data exchanges use **structured JSON** (`--json` flags) to avoid fragile text parsing
- Cache writes are **atomic** (`write_atomically` in [`src/utils.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/utils.rs)) to prevent data corruption
- The UI updates via **file-system events** (`notify` crate) without blocking the initial render
- **30-second timeouts** (`REMOTE_DETECTION_TIMEOUT`) prevent slow networks from hanging the interface

## Frequently Asked Questions

### How does Worktrunk avoid blocking the UI when checking CI status?

Worktrunk renders the initial interface immediately using cached data or empty placeholders. It spawns background processes via `Cmd::new(...).run()` in [`src/output/concurrent.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/output/concurrent.rs) to query `gh` or `glab`, then updates individual rows via file-system events when the cache updates. This ensures `wt list` remains responsive even during slow network conditions.

### What happens if the GitHub CLI is not installed?

If Worktrunk cannot detect the `gh` binary (or `glab` for GitLab), it skips the CI status display entirely. The picker continues to function normally, showing worktree information without the CI/PR badges. The tool does not crash or hang when CLI tools are missing.

### Where does Worktrunk store CI status cache files?

Worktrunk stores CI cache files under `.git/wt/cache/ci-status/<branch>.json` relative to the repository root. The [`src/cache.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/cache.rs) module handles reading these files, while [`src/utils.rs`](https://github.com/max-sixty/worktrunk/blob/main/src/utils.rs) contains the atomic write logic that ensures cache integrity during concurrent access.

### How does Worktrunk handle API rate limits or timeouts?

Background CLI processes inherit a `REMOTE_DETECTION_TIMEOUT` of approximately 30 seconds. Worktrunk relies on the underlying `gh` and `glab` CLIs to handle HTTP retries and rate limiting, simply terminating the subprocess if it exceeds the timeout threshold. Failed fetches result in empty CI columns rather than error messages breaking the UI.