The per-skill (avg X) column in gstack-analytics silently rendered as
"0s" for any /skill whose completion events emit "duration_s" as a
quoted JSON string. The completion-status preamble that every skill
runs (scripts/resolvers/preamble/generate-completion-status.ts:70) and
gstack-codex-probe both emit "duration_s":"<N>", so the dominant case
was wrong; gstack-telemetry-log emits the unquoted form which is why
the bug didn't show up everywhere.
The AVG_DUR awk block split each line on the literal "duration_s":
prefix and then ran gsub(/[^0-9.].*/, "", parts[2]). For unquoted
600,"ts":... the gsub matches at the comma and yields "600". For
quoted "600","ts":... it matches at the leading double-quote
(position 0) and the trailing .* consumes the entire rest of the
line, leaving an empty string that never accumulates.
The companion TOTAL_DURATION calculation (line 114) uses a different
parser keyed on -F'[:,]' field splitting + non-greedy gsub, so it
isolates the value before stripping and was always correct. Only the
per-skill average was broken.
Fix: strip an optional leading double-quote with sub(/^"/, ...) before
the existing gsub so quoted and unquoted forms take the same path.
Adds test/gstack-analytics-avg-duration.test.ts covering quoted-only,
unquoted-only, mixed, and total-time aggregation. The test fails on
the pre-fix tree (expected "2m", got "0s") and passes after the fix.