Skip to content

Flatten recovery.ts — eliminate recovery-of-recovery nesting pattern #178

@xliry

Description

@xliry

Problem

recovery.ts (132 lines) has 9 try blocks, 9 catch blocks. The core issue: recovery code has its own recovery code. recoverOrFailTask() has try/catch around every API call, and each catch has its own error handling. This creates deeply nested code that's hard to follow.

What to do

1. Create a safeLota() helper

Instead of try/catch around every lota() call:

async function safeLota(method: string, path: string, body?: object): Promise<{ ok: boolean; data?: unknown; error?: string }> {
  try {
    const data = await lota(method, path, body);
    return { ok: true, data };
  } catch (e) {
    return { ok: false, error: (e as Error).message };
  }
}

2. Flatten recoverOrFailTask() (lines 56-102)

Current: 4 try/catch blocks, 2 levels of nesting.

Refactor to sequential checks:

async function recoverOrFailTask(task, details, config): Promise<void> {
  const retryCount = task.retries ?? 0;

  if (retryCount < 3) {
    const nextRetry = retryCount + 1;
    log(`Recovering task #${task.id} (retry ${nextRetry}/3)`);
    
    const metaOk = await safeLota("PATCH", `/tasks/${task.id}/meta`, { retries: nextRetry });
    const statusOk = await safeLota("POST", `/tasks/${task.id}/status`, { status: "assigned" });
    const commentOk = await safeLota("POST", `/tasks/${task.id}/comment`, { content: `...` });
    
    if (!metaOk.ok || !statusOk.ok) {
      err(`Failed to recover task #${task.id}: ${metaOk.error || statusOk.error}`);
      return;
    }
    await safeLota(/* telegram */);
  } else {
    // ... same flat pattern for failure case
  }

  // Worktree cleanup
  if (details.workspace) {
    const wsPath = resolveWorkspacePath(details.workspace);
    if (wsPath && existsSync(wsPath)) cleanStaleWorktrees(wsPath);
  }
}

3. Flatten checkRuntimeStaleTasks() (lines 106-131)

Current: 2 try/catch blocks.

Refactor:

async function checkRuntimeStaleTasks(config): Promise<void> {
  const result = await safeLota("GET", "/tasks?status=in-progress");
  if (!result.ok) { dim(`Runtime stale-task check failed: ${result.error}`); return; }
  
  const tasks = result.data as StaleTask[];
  for (const task of tasks.filter(...)) {
    // ... flat sequential calls
  }
}

4. Separate Telegram calls

The Telegram try/catch blocks (lines 76-77, 89-90) are especially ugly — a failed notification shouldn't need its own catch. tgSend should handle its own errors internally (or safeLota pattern).

Check if tgSend already handles errors. If not, make it safe internally.

Target metrics

  • try blocks: 9 → 1 (only inside safeLota)
  • catch blocks: 9 → 1
  • Nesting depth: 3-4 → 1-2
  • Lines: ~132 → ~90-100

Files

  • src/recovery.ts — refactor
  • src/telegram.ts — check if tgSend needs to be made safe internally

Acceptance

  • No nested try/catch
  • All API calls use safeLota pattern
  • Same recovery behavior (retry 3x then fail)
  • Telegram failures don't create nested catch blocks

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions