One Step Eats Half Your Setup Time
If you time onboarding honestly, one phase swallows more than half of the wall-clock, and it is almost never the phase people argue about in the issue tracker. That is the entire conclusion, so the rest of this post is instrumentation. I am not here to tell you whether setup is easy or hard — I want to hand you a ledger that shows where your setup loses time, and then a rule for deciding which single fix is worth doing.
Why does the aggregate total lie to us so consistently? Because "setup took forty minutes" averages over phases that behave nothing alike: a network-bound auth handshake, a cold-start prompt, an edit round-trip, a test loop, a teardown. When you only track the total, you optimise whatever is most irritating instead of whatever is most expensive. The irritating step is loud and the expensive step is quiet, and quiet steps never file a bug report. So the first artifact here is a ledger, not a stopwatch.
Record every phase as a line, not a vibe
The harness below writes one JSONL row per phase, so you can re-run it after each change and diff the medians instead of trusting your memory of how long things felt.
#!/usr/bin/env bash
# friction.sh — one onboarding phase per JSONL line.
set -uo pipefail
LEDGER="${LEDGER:-friction.jsonl}"
now() { date +%s.%N; }
record() { # record <phase> <seconds> <rc> <note>
printf '{"phase":"%s","seconds":%.3f,"rc":%s,"note":"%s","host":"%s"}\n' \
"$1" "$2" "$3" "$4" "$(hostname)" >> "$LEDGER"
}
run_phase() { # run_phase <phase> <note> -- <command...>
local phase="$1" note="$2"; shift 2
[ "$1" = "--" ] && shift
local t0 t1 rc
t0=$(now)
"$@" >/tmp/friction.out 2>/tmp/friction.err
rc=$?
t1=$(now)
record "$phase" "$(echo "$t1 - $t0" | bc)" "$rc" "$note"
return $rc
}
Wrapping your own client is then a five-line affair. The invocation shape below is illustrative rather than executed here, so substitute whatever your tooling actually accepts — the point is the phase boundaries, not the flags.
# Illustrative only — replace with your own client's commands.
export LEDGER=friction-run1.jsonl
run_phase auth "device flow + token exchange" -- ./your-cli auth login
run_phase first_prompt "cold start to first token" -- ./your-cli ask "print the repo tree"
run_phase first_edit "edit one function, write file" -- ./your-cli edit --file src/app.py --patch patch.diff
run_phase first_test "loop until unit tests are green" -- ./your-cli run -- pytest -q
run_phase teardown "drop workspace, revoke token" -- ./your-cli cleanup --yes
Notice that rc is recorded next to the duration, and that matters more than it looks. A phase that fails in two seconds is not a fast phase; it is an unfinished phase, and if you drop those rows you will happily "optimise" the one step that never actually completed.
Turn the ledger into a single number you can argue about
Five runs later you have a pile of lines and no opinion. This report script reduces it to medians and one ratio: how much of the total the slowest phase owns.
#!/usr/bin/env python3
"""friction_report.py — median per phase plus the concentration ratio."""
import collections, json, statistics, sys
rows = [json.loads(line) for line in open(sys.argv[1]) if line.strip()]
ok = [r for r in rows if r["rc"] == 0]
by_phase = collections.defaultdict(list)
for r in ok:
by_phase[r["phase"]].append(r["seconds"])
med = {p: statistics.median(v) for p, v in by_phase.items()}
total = sum(med.values())
ranked = sorted(med.items(), key=lambda kv: -kv[1])
for phase, seconds in ranked:
print(f"{phase:<14} {seconds:7.1f}s {seconds / total:7.1%} n={len(by_phase[phase])}")
top_share = ranked[0][1] / total
print(f"\ntotal median wall-clock: {total:.1f}s")
print(f"concentration ratio: {top_share:.2f} (top phase: {ranked[0][0]})")
print("verdict: fix that one phase, then re-measure." if top_share > 0.5
else "verdict: no dominant phase; hunt shared overhead first.")
I use the median rather than the mean on purpose, because one cold image pull will drag an average somewhere unhelpful while the median keeps telling you what a normal run costs. Five runs per configuration on a disposable machine is the minimum I would trust, and ten is better if your network is noisy. The concentration ratio is the number I take into a review meeting, because it converts a feeling into a claim somebody can dispute.
| Concentration ratio | What it means | What to do next |
|---|---|---|
| > 0.60 | one phase owns the clock | fix that phase only, then re-run five times |
| 0.40 – 0.60 | two phases share it | fix whichever has the higher variance |
| < 0.40 | overhead is spread thin | look at shared plumbing: image pulls, DNS, proxy, disk |
any rc != 0
|
the phase failed, not finished | the failure is the finding; fix it before timing again |
Measuring on a free environment changes the question
Here is where the free tier of an open-source tool genuinely participates in the method rather than decorating it. MonkeyCode offers free model access and a free server option, which the operator states is currently accompanied by a ten-million-token allowance; treat both as time-bound and verify the current terms on the project page, because free-tier terms move. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The practical value is that a throwaway environment removes the credit card from the loop, so you can run the ledger twice — once on your laptop, once on the hosted box — and compare the ordering of phases rather than the raw seconds. If auth is third on your machine and first on the server, the bottleneck is environmental and no amount of script cleanup will fix it. That is a cheap experiment to get wrong twice, and an expensive one to skip. The fix only counts as done when the ledger says the concentration ratio moved, not when the PR merges.
Limitations, and who should walk away
This measures elapsed time, and elapsed time is blind to comprehension: a phase can be fast and still leave a new contributor confused about why they are typing it. The harness also adds a little overhead per run, it says nothing about steady-state productivity after day one, and single runs are noise dressed as data. Free tiers rate-limit, change shape, and occasionally disappear, so never build an on-call rotation on top of one. If your organisation needs audited dedicated capacity, contractual SLAs, or strict data-residency guarantees, this whole approach is the wrong tool and you should stop reading here.
So what does your own concentration ratio look like — and are you prepared to be wrong about which step was the slow one? Copy the two scripts, run them five times on a disposable environment, and fix exactly one thing. If you would rather not stand up that environment yourself, the MonkeyCode project is a reasonable free place to start, but the ledger is what earns the next merge, not the launch command.
Top comments (0)