# T13.19: Job polling with backoff/timeout in Raycast extension

## Description

## Summary

Implement robust job polling in the Raycast extension with:

- Request timeouts
- Exponential backoff with jitter
- Maximum poll duration
- Graceful handling of edge cases

## Implementation

```typescript
interface PollOptions {
  maxDuration?: number; // Max total poll time (default: 5 min)
  initialInterval?: number; // Starting interval (default: 250ms)
  maxInterval?: number; // Cap interval growth (default: 2000ms)
  timeout?: number; // Per-request timeout (default: 5000ms)
}

async function pollJob(
  jobId: string,
  onProgress?: (job: JobStatus) => void,
  options: PollOptions = {}
): Promise<JobStatus> {
  const {
    maxDuration = 5 * 60 * 1000,
    initialInterval = 250,
    maxInterval = 2000,
    timeout = 5000,
  } = options;

  const startTime = Date.now();
  let interval = initialInterval;

  while (Date.now() - startTime < maxDuration) {
    try {
      const res = await fetch(`${getApiBase()}/api/jobs/${jobId}`, {
        signal: AbortSignal.timeout(timeout),
      });

      if (res.status === 404) {
        // Job expired or server restarted
        throw new Error("Job expired. Please try again.");
      }

      const job: JobStatus = await res.json();
      onProgress?.(job);

      if (job.status === "completed" || job.status === "failed") {
        return job;
      }
    } catch (err) {
      if (err instanceof Error && err.name === "TimeoutError") {
        // Request timed out, continue polling
      } else if (err instanceof TypeError) {
        // Network error - server may have stopped
        throw new Error("Lost connection to GNO server");
      } else {
        throw err;
      }
    }

    // Exponential backoff with jitter
    const jitter = Math.random() * 0.3 * interval;
    await new Promise((r) => setTimeout(r, interval + jitter));
    interval = Math.min(interval * 1.5, maxInterval);
  }

  throw new Error("Operation timed out. Check server status.");
}
```

## Edge Cases Handled

1. **Server stopped** - TypeError on fetch → 'Lost connection'
2. **Job expired** - 404 → 'Job expired'
3. **Slow job** - Backoff prevents hammering
4. **Stuck job** - maxDuration prevents infinite polling

## Parent

Part of Raycast epic gno-ub9.

## Acceptance

- [ ] Implementation complete
- [ ] Tests passing
- [ ] Documentation updated

## Done summary

Superseded by the existing GNO MCP + installed skill workflow. Raycast-specific implementation no longer planned.

## Evidence

- Commits:
- Tests:
- PRs:
