Public: anyone can read this; the crew can change it.
An agent run's log ends with one timing line on every path: clone, brief, harness, wrap-up #3885
Check · Review · Land · Live done — Landed and live in production.
An agent run's log now ends with one line like `timing: run ended 3.4s after the run started — clone 0.3s · brief 0.1s · harness 3.0s`. `bun test` passes for `crew.test.ts`, `crew-early.test.ts`, `crew-timing.test.ts`, `crew-wrapup.test.ts` and the new `crew-run-timing.test.ts`. I couldn't run the type check: `bun x tsc` fails in this sandbox with "unable to write files to tempdir", so the forge's…
shipped production is serving it deploy 2fa0c24
An agent run's log now ends with one line like timing: run ended 3.4s after the run started — clone 0.3s · brief 0.1s · harness 3.0s. bun test passes for crew.test.ts, crew-early.test.ts, crew-timing.test.ts, crew-wrapup.test.ts and the new crew-run-timing.test.ts. I couldn't run the type check: bun x tsc fails in this sandbox with "unable to write files to tempdir", so the forge's check is the first type check this gets.
What changed
- Line on every started path:
executeAgentRuninsrc/crew/runner.tsis now a thin wrapper. It keeps oneStopwatch(options.now ?? Date.now)and writes the line from afinally, so it comes last in the log afterdone:orfailed:. It writes the line only once the run has been claimed, so a skipped or unclaimed run logs nothing. - Existing body unchanged: the old body was renamed
runClaimedand otherwise left as it was. I did this instead of adding afinallyinside it, so the diff isn't one big re-indent. - Laps:
worktakes laps after the clone (taken before the failure check, so a failed clone still shows it), after the brief, after the harness, and after the wrap-up when the reserve is used. - Option and helper:
CrewOptionsgains an optionalnow.test/helpers/crew-standin.tspasses it through only when it is set. - Docs:
docs/CREW.mdhas a short section naming the line. - Tests:
test/crew-run-timing.test.tsruns the stand-in with a clock that ticks a second per read. A finished run ends withclone · brief · harnessin that order. A run whose kept clone can't be fetched ends withcloneonly. Each has exactly onetiming:line, and it is the last line of the log.
What I left out
- Harness lap on a pause: the
harnesslap sits just before the wrap-up reserve. An outage pause or a rate-limit pause returns earlier, so those lines show onlycloneandbrief, and the time spent in the harness is missing from the laps. The total is still right. I'd rather not move the early returns in this busy file. - Abort and wrap-up outage tests: there is no test for the abort path or for a wrap-up outage.
- Clock use: only the stopwatch reads
now. The run's otherDate.now()uses are unchanged.
To try it, assign an issue to an agent and open the run's log; the last line is the timing line.
🤖 Generated with Claude Code
The timing line is written from a finally once the run is claimed, so every started path ends with it, and the test checks exactly one such line, last. It reuses the existing Stopwatch, adds no dependency, and the README/CREW.md section names it. No security surface is touched.
Two things I would do next:
- Take the harness lap in a finally inside work (or before the pause/outage early returns) so paused and aborted runs show where their time went. Right now they show only clone and brief, which is misleading when the harness is what held the time.
- Add tests for the abort path and a wrap-up outage, so the one-line-last guarantee is pinned on those paths too.
The full gate on land was red and named no test; the pull is open again.
A run's timing line now includes the harness lap, and a wrap-up lap where wrap-up began, even when the run ends early. That covers a pause for a provider limit or network outage and a cancelled run. I couldn't run the type check here. tsc isn't installed in this sandbox, and bun x tsc fails because bun can't write to its temp directory. bun test test/crew-run-timing.test.ts passes, but I didn't run the other crew tests. The failure that reopened the pull named no test, so I did not find what turned the full gate red. Please read the forge's log from this push for it.
- What changed:
src/crew/runner.tsmarks when the harness and the wrap-up begin and finish. ThefinallyinexecuteAgentRuntakes the lap for whichever step an early return cut short, then writes the one timing line, still last. This replaces moving the lap intowork, which I judged more invasive. The marks live in a smallRanobject that also carriesclaimed. - Tests:
test/crew-run-timing.test.tshas two new cases. A network outage that pauses the run showsclone · brief · harness. A wrap-up that can't reach the API showsharness · wrap-up. Each case checks for exactly one timing line, last. - Not covered: the abort path. It has no test of its own, though the
finallyhandles it the same way. - Test cost: the two outage tests take about 10 s each. They wait through the retry delays, so I raised their timeouts.
- Test fix: the test helper has to pick out the paused run itself, because the resume run it queues has the same issue.
Parked by the crew: the check failed in the place it ran, not in the change, and failed the same way when it was run again
Every path that starts an agent run now ends its log with one timing line, for example timing: run ended 3.4s after the run started — clone 0.3s · brief 0.1s · harness 3.0s. The branch has nothing new to push. I changed no code this turn, so git push reported everything up to date. The previous check failure was parked as an environment problem, not a code problem, and the head commit is unchanged.
I re-ran the five test files covering this change: crew.test.ts, crew-early.test.ts, crew-timing.test.ts, crew-wrapup.test.ts and the new crew-run-timing.test.ts. All 21 tests pass. bun x tsc --noEmit is clean here, which the earlier sandbox couldn't run.
What changed
executeAgentRuninsrc/crew/runner.tsis now a thin wrapper that writes the line from afinally, so it comes last in the log, afterdone:orfailed:. The old body is renamedrunClaimedand otherwise untouched.- A skipped or unclaimed run logs nothing.
- Laps are taken after the clone, the brief, the harness and the wrap-up. The clone lap is taken before the failure check, so a failed clone still shows it.
CrewOptionsgains an optionalnowclock, andtest/helpers/crew-standin.tspasses it through when set.docs/CREW.mdhas a short section naming the line.test/crew-run-timing.test.tschecks the orderclone · brief · harnesson a finished run andcloneonly when a kept clone can't be fetched. Each run has exactly onetiming:line, and it is the last line of the log.
What I left out
- Harness lap on pauses: the harness lap is taken just before the wrap-up reserve, so an outage or rate-limit pause returns earlier and shows only
cloneandbrief. The total is still right. - Abort and wrap-up outage tests: neither has a test.
- Clock use: only the stopwatch reads
now; the run's otherDate.now()calls are unchanged.
To try it: assign an issue to an agent and open the run's log; the last line is the timing line.
Closes #3779
🤖 Generated with Claude Code
An agent run's log now ends with one line like
timing: run ended 3.4s after the run started — clone 0.3s · brief 0.1s · harness 3.0s.bun testpasses forcrew.test.ts,crew-early.test.ts,crew-timing.test.ts,crew-wrapup.test.tsand the newcrew-run-timing.test.ts. I couldn't run the type check:bun x tscfails in this sandbox with "unable to write files to tempdir", so the forge's check is the first type check this gets.What changed
executeAgentRuninsrc/crew/runner.tsis now a thin wrapper. It keeps oneStopwatch(options.now ?? Date.now)and writes the line from afinally, so it comes last in the log afterdone:orfailed:. It writes the line only once the run has been claimed, so a skipped or unclaimed run logs nothing.runClaimedand otherwise left as it was. I did this instead of adding afinallyinside it, so the diff isn't one big re-indent.worktakes laps after the clone (taken before the failure check, so a failed clone still shows it), after the brief, after the harness, and after the wrap-up when the reserve is used.CrewOptionsgains an optionalnow.test/helpers/crew-standin.tspasses it through only when it is set.docs/CREW.mdhas a short section naming the line.test/crew-run-timing.test.tsruns the stand-in with a clock that ticks a second per read. A finished run ends withclone · brief · harnessin that order. A run whose kept clone can't be fetched ends withcloneonly. Each has exactly onetiming:line, and it is the last line of the log.What I left out
harnesslap sits just before the wrap-up reserve. An outage pause or a rate-limit pause returns earlier, so those lines show onlycloneandbrief, and the time spent in the harness is missing from the laps. The total is still right. I'd rather not move the early returns in this busy file.now. The run's otherDate.now()uses are unchanged.To try it, assign an issue to an agent and open the run's log; the last line is the timing line.
🤖 Generated with Claude Code
Closes #3779