Skip to content
Back to skills

Delivery Forensics

ASecurity

Measure buildd's own delivery loop from raw sources — prod DB, GitHub Actions, the Coder runner and the KB. Use when asked why PRs conflict, why CI fails, how long the fail→resolve loop takes, whether PRs merge eventually, or for any "analyse the last N missions/PRs" question. Carries the access recipes that are non-obvious or actively broken.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 5, 2026
ai-agentsgobashsqlnodegitapidatabase

Works with

  • terminal
  • cli
  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned October 5, 2026

npx -y skills add buildd-ai/buildd --skill delivery-forensics --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Delivery Forensics?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Delivery Forensics
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/buildd-ai-delivery-forensics/badge)](https://www.skillsdirectory.com/skills/buildd-ai-delivery-forensics)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: delivery-forensics
description: Measure buildd's own delivery loop from raw sources — prod DB, GitHub Actions, the Coder runner and the KB. Use when asked why PRs conflict, why CI fails, how long the fail→resolve loop takes, whether PRs merge eventually, or for any "analyse the last N missions/PRs" question. Carries the access recipes that are non-obvious or actively broken.
---

# Delivery forensics

Four sources. Each answers something the others cannot. Use the DB for
*outcomes*, GitHub for *causes*, the runner for *liveness*, the KB and the code
for *why the code is shaped that way*.

## 1. Prod DB — the only place outcomes live

**Direct `psql` to Neon times out from a laptop** (5432 is not reachable; the
sandbox is not the problem — `dangerouslyDisableSandbox` fails identically).
Go over the neon HTTP driver instead, which is what the app itself uses.

```bash
S=<scratchpad>
grep -m1 '^DATABASE_URL=' apps/web/.env.local | sed 's/^DATABASE_URL=//; s/^"//; s/"$//' > $S/dburl
bun .claude/skills/delivery-forensics/scripts/q.ts $S/dburl $S/queries.sql
```

`apps/web/.env.local` is **live prod**. The repo-root `.env` is a stale Feb-era
branch — never analyse from it. `vercel env pull` returns no `DATABASE_URL`.

`q.ts` splits on `;\n`, prints one TSV block per statement, and prints `ERR <msg>`
instead of aborting, so a whole batch survives one bad column name.

### Which column answers which question

| Question | Where |
|---|---|
| Did the PR merge, and when | `workers.merged_at`, `workers.pr_lifecycle_status` (`merged`/`closed`/`ci_running`/`pr_open`/`unresolvable`) |
| How many times CI went red on it | `workers.pr_check_failure_count` |
| Was it a conflict | `workers.conflict_detected_at`, `workers.pr_unresolvable_reason` |
| Was this task remediation or real work | `tasks.task_class` ∈ `work` / `attempt` / `bookkeeping` |
| Which remediation kind, and for which PR | `tasks.ci_retry_pr_number` / `conflict_retry_pr_number` / `reviewer_retry_pr_number` |
| Retry rounds on one PR | `count(*)` of the above grouped by the PR number |
| Why the worker stopped | `workers.exit_cause` (`code_failure`/`infra_failure`/`sandbox_mount_gap`/`silent_start`/`budget_limited`/`never_started`) |
| Collision *avoidance* coverage | `path_claims` — join back to `tasks` to get the adoption rate; zero contention means low adoption, not a working lock |
| Mission rollup | `missions` + `tasks.mission_id` + `workers.task_id`; `missions.integration_branch_enabled`, `merge_policy` |

Gotchas:
- **Four PR columns are effectively unpopulated — do not build a metric on
  them** (verified by direct count against the live table): `conflict_detected_at`,
  `pr_unresolvable_reason`, and `pr_check_failure_count > 0` are each nonzero on
  only a handful of rows table-wide, and `files_changed` is zero on every
  integration PR while GitHub reports real sizes. So the `pr_unresolvable_reason`
  *mix* is not determinable from the DB at all. Dense substitutes: remediation
  dispatch (`tasks.*_retry_pr_number`) for "went red / conflicted", the Actions
  API for CI outcomes, and `gh` for PR size.
- `worker_error_traces` has **no `created_at`** — check
  `information_schema.columns` before writing a windowed query.
- Retry-task *status* matters as much as count: a large `cancelled` share means
  the remediation was thrown away, not that it succeeded.
- `tasks.title` carries the loop depth in prose (`[Conflict Retry #1] [reviewer
  retry #2] …`). Grouping on `regexp_replace(title,'^\[[^]]+\]\s*','')` finds
  duplicate remediation of the same subject.

## 2. GitHub Actions — the only place causes live

```bash
# runs: paginate; ~250 runs/day, so 18 pages ≈ 7 days
for p in $(seq 1 18); do
  gh api "/repos/buildd-ai/buildd/actions/runs?per_page=100&page=$p" \
    -q '.workflow_runs[]|[.id,.name,.head_branch,.head_sha[0:7],.event,.status,.conclusion,.run_attempt,.created_at,.updated_at]|@tsv'
done > $S/runs.tsv
```

**`gh run view <id> --log-failed` returns empty output and exit 0.** It is not a
retention problem. Go through the jobs API:

```bash
gh api "/repos/buildd-ai/buildd/actions/runs/$id/jobs" \
  -q '.jobs[]|select(.conclusion=="failure")|.id'
gh api --allow-escape-sequences "/repos/buildd-ai/buildd/actions/jobs/$jid/logs" | sed 's/^[0-9T:.\-]*Z //'
```

The job-log body routinely contains raw terminal escape sequences (colored
test output, progress bars). `gh` refuses to print those to a terminal by
default and errors instead of returning the log — pass `--allow-escape-sequences`
up front rather than re-running after the first attempt comes back empty.

Failing *step* names are the cheap first cut — get them from the same jobs call
(`.steps[]|select(.conclusion=="failure")|.name`) before pulling any log.

To name the failing tests, the unit-test digest is at the tail of the `build`
job log, between `unit test files failed:` and `Full output`:

```bash
awk '/unit test files? failed:/,/Full output/'
```

`No Production Data` findings are **masked on purpose** (public repo,
world-readable logs) — you get the category (`PR body` / `commit message` /
`added code`) and the reason, never the value. Categorise on the `##[error]`
line; `^(ERROR|FAIL)` does not match it.

Read `cancelled` as "superseded by a newer push", not as a failure.

## 3. Coder runner — liveness only

See the `coder-workspace` skill for SSH and the restart traps.

`/home/coder/.buildd/logs/*.log` hold **structured events only** — no agent
transcript, no test output, no conflict text. `session_start.detail` is the one
useful field (mode, worktree path). Do not plan a failure analysis around these
logs; grepping 166 of them for "conflict" yields five hits, all of them the task
title. `claims.log` is real signal for starvation (`diagnosticReason`).

`curl -s localhost:8766/api/version` → `currentCommit` vs `diskCommit`/`commitDrift`.

## 4. KB and code

- `recall` (`scope:["memory","task"]`) — thin on delivery-loop questions; it
  indexes task summaries, so it answers "has this exact failure been seen"
  better than "what is our conflict rate".
- Search the code (`rg`) for the *policy* behind a
  number, e.g. `DEFAULT_MAX_CI_RETRIES` in `apps/web/src/lib/ci-retry.ts`,
  `classifyMergeFailure` / `dispatchConflictRetry` in `conflict-retry.ts`.

## 5. Base drift — measured, and it does NOT predict merge

**Corrected by direct measurement; the earlier claim here was wrong.** Base
drift does not predict merge probability on this repo. Measured non-circularly
(drift in a PR's first 6h, so the window does not depend on the outcome),
merge probability *rises* — not falls — as drift climbs, and across deciles it
stays consistently high and flat. The apparent cliff at the high-drift tail is
a handful of PRs and is an **artifact of measuring to `now()` for still-open
PRs** — a still-open PR accumulates drift precisely because it has not merged,
so naive drift is circular. If you compute drift, bound the window
independently of the outcome.

The arithmetic explains why: at this repo's merge cadence on `dev` and its
median time-to-merge, a typical PR sees its base move a fraction of a single
merge. Drift cannot drive an outcome it never gets the chance to touch.

**Use PR size instead** — it separates cleanly: open PRs run several times
larger at the median than merged ones.

Drift is still worth computing for one narrower question: comparing *branch
strategies*. An integration branch has a measurably slower-moving base than
trunk, and that does track a lower conflict-retry rate — conflict-retry, not
final merge, since most conflicted PRs merge anyway.

Script (keep the outcome-independent window in mind):

```bash
bun .claude/skills/delivery-forensics/scripts/base-drift.ts $S/prs.json
```

Feed it `gh pr list --state all --limit 300 --json number,title,state,createdAt,mergedAt,baseRefName,changedFiles`.
Do **not** add `author`, `commits`, or `reviews` at that limit — the GraphQL node
budget rejects the query.

Concurrent-file contention needs git, not the API: `git log origin/dev --since=…
--pretty=format:'@@%H|%s' --name-only`, map `(#NNNN)` in the squash subject back
to the PR, and intersect file sets across overlapping lifetimes.

Files in this skill

  • SKILL.md8.1 KB
  • scripts/base-drift.ts2.4 KB
  • scripts/q.ts1.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.