---
name: run-backend
description: Kill stale .Api processes, launch dotnet run, verify /health — retry loop
group: RUN
recommended_model: haiku
allowed-tools: [Read, Glob, Grep, Bash]  # Bash: process management
---

# Skill: Run Backend — kill → launch → verify → retry

## Inputs

- Worktree cwd (you're already there)
- AppCode: read from `src/*.Api.csproj` filename, or from `.smartstack/config.json`
- Expected port: `--urls` arg OR 5142 default

## Loop (max 3 attempts)

```
attempt = 1
LOOP:
  # ── 1. Kill stale processes ────────────────────
  Get-Process | Where-Object { $_.Name -like '*.Api' -and $_.Path -like '*{worktree}*' } | Stop-Process -Force
  # OR on Unix: pkill -f "{AppCode}.Api"
  # OR generic: npx fkill-cli "{AppCode}.Api.exe" 2>/dev/null
  Free port: netstat -ano | findstr :5142 → taskkill /PID <pid> /F

  # ── 2. Build (fail fast) ───────────────────────
  dotnet build --nologo --verbosity minimal src/{AppCode}.Api
  IF exit != 0:
    capture last 20 lines → stderr_summary
    IF stderr_summary == prev_stderr_summary: break (non-transient, escalate)
    attempt++ → CONTINUE LOOP
  END

  # ── 3. Launch ──────────────────────────────────
  Spawn: dotnet run --no-build --no-launch-profile --urls http://localhost:5142 --project src/{AppCode}.Api
  (spawn in background, capture stdout/stderr)

  # ── 4. Wait + verify ──────────────────────────
  FOR i in 1..15:   # 15 * 2s = 30s max
    sleep 2s
    response = curl -fsS http://localhost:5142/health || curl -fsS http://localhost:5142/
    IF HTTP 2xx/4xx:
      # ── 4b. DB connectivity check (port OK ≠ DB OK) ──
      # Parse /health body for a DB/SqlServer entry. If the health payload
      # exposes database status, require it to be "Healthy" before SUCCESS.
      # If /health returns plain 200 with no body, probe an endpoint that
      # requires DB (e.g. /api/navigation/menu returning 401 Unauthorized
      # proves routing + DB up; 500 or ECONNREFUSED means DB dead).
      nav_response = curl -s -o /dev/null -w "%{http_code}" http://localhost:5142/api/navigation/menu
      IF nav_response in (200, 401, 403): return SUCCESS
      IF nav_response == 500: continue (DB still warming up)
    END
  END

  # ── 5. Post-mortem before retry ───────────────
  Check if process still alive
    IF dead: read last 30 lines of stdout → note the error
    IF alive but port refused: Kestrel bound elsewhere → check logs for "Now listening on:"
  attempt++
END LOOP

IF attempt > 3: FAIL with summary of what was tried
```

## Recognized transient errors (auto-retry, no escalation)

- `MSB3026: Impossible de copier ... locked by ...` → orphan not killed yet, wait 2s + retry
- `bind: address already in use` → port conflict, kill occupant + retry
- `A connection attempt failed because the connected party did not properly respond` (flaky SQL) → wait 3s + retry

## Non-transient errors (stop + escalate)

- `error CS\d+` with same code 2x in a row → compile error, escalate to `debug/backend`
- `Microsoft.Data.SqlClient.SqlException: Login failed` → config issue, escalate
- `System.Reflection.TargetInvocationException` → DI, escalate
- `Unable to find package` → dependency problem, escalate

## Output format (Haiku-friendly, concise)

Success:
```
[backend] ✓ OK (port 5142, HTTP 200 in 6.2s, attempt 1)
```

Transient retry:
```
[backend] attempt 1: MSB3026 (DLLs locked), killed PID 7841
[backend] attempt 2: ✓ OK (port 5142, attempt 2)
```

Escalation needed:
```
[backend] attempt 1: error CS0246 in UserService.cs:42 'IUserRepository' not found
[backend] attempt 2: same CS0246 — non-transient
[backend] ✗ FAIL. Run: claude -p "@.claude/skills/development/debug/backend/SKILL.md"
```

## Rules

- **Kill before launch, always** — `--no-build` fails if DLLs are locked by orphans
- **One command per step** — no shell piping spaghetti
- **Report reality** — `curl` the URL, don't trust "Application started"
- **No source modification** — escalate to debug for that
- **Max 3 attempts** — budget strictly enforced
