jackwener/OpenCLI · error · Error

Detached HEAD — checkout a branch first

Error message

Detached HEAD — checkout a branch first

What it means

At src/daemon.ts:496 the daemon's HTTP server (httpServer, declared from createServer) wraps every handleRequest invocation with a catch that discards the error and replies with a bare 500 and no body: res.writeHead(500); res.end(). Clients therefore see an empty message — the response carries status 500 but no error JSON explaining what failed. The declared symbol region is this server-creation site; the empty message is the deliberate (but unhelpful) swallowing of the underlying rejection.

Source

Thrown at autoresearch/engine.ts:103

  /** Phase 0: Precondition checks */
  private checkPreconditions(): void {
    // Git repo exists
    try { execStrict('git rev-parse --git-dir'); }
    catch { throw new Error('Not a git repository'); }

    // Clean working tree
    const status = exec('git status --porcelain');
    if (status) throw new Error(`Working tree not clean:\n${status}`);

    // No stale locks
    if (existsSync(join(ROOT, '.git', 'index.lock'))) {
      throw new Error('Stale .git/index.lock found — remove it first');
    }

    // Not detached HEAD
    try { execStrict('git symbolic-ref HEAD'); }
    catch { throw new Error('Detached HEAD — checkout a branch first'); }
  }

  /** Phase 5: Run verify command and extract metric */
  private runVerify(): number | null {
    this.log('  verify...');
    const output = exec(this.config.verify, { timeout: 300_000 });
    return extractMetric(output);
  }

  /** Phase 5.5: Run guard command */
  private runGuard(): boolean {
    if (!this.config.guard) return true;
    this.log('  guard...');
    try {
      execStrict(this.config.guard, { timeout: 300_000 });
      return true;
    } catch {
      return false;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Check daemon logs at the time of the request — the rejection is logged server-side even though the client sees an empty 500.
  2. Change the catch handler to send a JSON error body (e.g. jsonResponse(res, 500, { ok:false, error: message })) so failures are diagnosable from the client side.
  3. Reproduce the request with curl and verify the payload matches the endpoint's expected schema (valid JSON, correct fields).
  4. Ensure routes wrap their logic in the existing try/catch that maps DaemonCommandFailure and timeouts to proper status codes (400/408).

Example fix

// before
handleRequest(req, res).catch(() => { res.writeHead(500); res.end(); });
// after
handleRequest(req, res).catch((err) => {
  jsonResponse(res, 500, { ok: false, error: err instanceof Error ? err.message : 'Internal error' });
});
Defensive patterns

Strategy: validation

Validate before calling

const body = JSON.stringify(payload);
JSON.parse(body); // fail fast client-side before hitting the daemon
const res = await fetch(url, { method: 'POST', body });
if (res.status === 500 && (await res.text()).length === 0) {
  console.error('Daemon returned empty 500 — check daemon logs for the swallowed handleRequest rejection');
}

Type guard

function isDaemonErrorResponse(res: Response): res is Response & { status: 400 | 408 | 500 } {
  return res.status >= 400;
}

Try / catch

const res = await fetch(daemonUrl, { method: 'POST', body });
if (!res.ok) {
  const text = await res.text();
  if (!text) throw new Error(`Daemon 500 with empty body (src/daemon.ts:496) — inspect daemon logs`);
  const { error } = JSON.parse(text);
  throw new Error(error);
}

Prevention

When it happens

Trigger: Any HTTP request to the daemon where handleRequest's promise rejects — malformed JSON body, handler bug, or thrown DaemonCommandFailure outside the handled branch — triggering the .catch(() => { res.writeHead(500); res.end(); }) fallback with an empty response body.

Common situations: A client sends an unparseable POST body to a daemon endpoint; an internal handler throws before the route-level try/catch at line 479 can respond; the request is aborted mid-handler causing a downstream rejection; a bug in a new route escapes the existing error mapping.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/ee39f3ecaf45005a. Report an issue: GitHub.