You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
http_request_duration_ms and http_request_errors_total still carry #9650's coverage hole — the p95-latency half of the operator guidance sees dispatcher routes only #9834
Found while implementing the transport-owned http_requests_total seam for #9650 (PR #9746). Filed unassigned; not graded by the filing seat.
The claim, read on 8957c72227
#9650's ruling names one metric, and PR #9746 moves exactly that one to the transport. Its two siblings in the same SEMCONV HTTP family are still emitted only from instrumentRouteHandler (packages/runtime/src/observability/instrument.ts:106-113), applied only by the dispatcher's own Proxy (packages/runtime/src/dispatcher-plugin.ts:700-721):
So the identical blind spot #9650 was filed about — auth's getRawApp() mount, the REST data API via RouteManager, and the other measured surfaces in those two classes — remains for both.
p95 latency, from http_request_duration_ms — still dispatcher-only.
An operator reading one dashboard would see request volume for /api/v1/* next to a latency panel that has no series for it at all. A missing panel is at least loud; the worse reading is the p95 that is drawn, computed from dispatcher routes only and presented as the server's.
packages/observability/src/semconv.ts:22 declares the whole family as the stable namespace "so hosts can wire alerts/dashboards against it", which is the same declared-not-enforced shape #9650 records.
The 2026-08-18 ruling is explicit and narrow: emit http_requests_total from the adapter. Adding two more metric families at the same seam is a wider behaviour change than was ruled, and it interacts with the duplicate-emission question the sibling card records — the histogram would double-count the dispatcher's routes the same way. Sequencing matters, so it is filed rather than smuggled in.
Shape a fix would take
The transport seam already reads everything both need: elapsed time around await next(), the matched pattern, and the status. The open questions are ownership (same de-duplication decision as the sibling card) and whether the error counter's meaning survives the move — instrumentRouteHandler increments it on a thrown handler, which the adapter catches in runHandler before Hono's error path, so the transport-level condition is not the same condition and must be defined rather than assumed.
Triage: lands in domain:cli. Bug (declared-not-enforced restoration for two more SEMCONV HTTP family members — same shape as #9650, not a new declared key), pm:queue+pm:blocked. Sequenced behind #9833 because the histogram would double-count the dispatcher's routes the same way, and de-duplication ownership is the same open question in both — resolve that once, apply to both metric families, rather than re-litigating per card.
Measurement update — #9835 did NOT close this card
Posting so nobody generalises from the sibling note I just left on #9833.
#9835 (7ff3975070) landed after this card's family was filed and armed the transport-wide HTTP counter through the IHttpResponse.afterResponse seam. It closed the equivalent hole for http_requests_total — enough that #9650's http_requests_total half is now stale (recorded there, comment 5340467871) and #9833's double-count mechanism appears structurally impossible (recorded there, comment 5340837275).
It does not touch this card. Measured on origin/main @ 5989b0de90 — armHttpRequestCounter (packages/observability/src/http-transport-metrics.ts) registers exactly one observer, and that observer emits exactly one family:
RUNTIME_METRICS.httpRequestsTotal and nothing else. No http_request_duration_ms, no http_request_errors_total.
⇒ This card's claim stands exactly as written: the p95-latency half of the operator guidance still sees dispatcher routes only. Its pm:blocked state is unchanged and its scope has not narrowed.
⚠️ For whoever picks this up: the seam #9835 built is the obvious vehicle (afterResponse already carries method, routePattern and status, and the per-server Symbol.for latch already solves the one-owner problem) — but whether a duration observation belongs on that same seam is a real question, not a mechanical extension: the observation carries no timing today, and currentPerfTiming lives in a different layer. Measure before assuming the seam is sufficient.
Blocker changed, not cleared — this is now same-file serial, and I am not dispatching it yet
State update from the domain:cli seat (session session_01WeN7F6jQFpcqW2BN56RdPa).
The card-level blocker is gone
#9832 landed (PR #9951, cb6c82194a) and parent #9650 is closed (discharged on measurement — the http_requests_total half is genuinely covered now; see #9650's closing comment). This card was next in line to dispatch.
But it cannot start yet, and the reason is mechanical
Its fix necessarily edits packages/runtime/src/dispatcher-plugin.ts, which is held by the in-flight #9936 + #9961 family (branch claude/issue-9936-sse-fallback-envelope). Same file is hard serial in this lane (maintainer-reaffirmed 2026-08-19), so this waits.
I checked that rather than assuming it. packages/runtime/src/observability/instrument.ts:40-46 says so in its own words:
Only the counter is gated: request-id echo, the duration histogram, the error counter and the error reporter are not emitted by the transport seam and always stay on.
So emitHttpRequestsTotal gates exactly one family (:79, :126), while httpRequestDurationMs (:134) and httpRequestErrorsTotal (:116) are unconditional. Moving either to the transport requires gating it the same way — a new flag in instrument.tsand the matching argument at the Proxy's call site in dispatcher-plugin.ts. There is no version of this fix that leaves that file alone.
Pre-established for whoever picks it up — so the next dispatch starts warm
File surface (expected):packages/observability/src/http-transport-metrics.ts (the seam — today it registers one observer emitting only RUNTIME_METRICS.httpRequestsTotal) · packages/runtime/src/observability/instrument.ts (the gating flags) · packages/runtime/src/dispatcher-plugin.ts (the call site) · a pin · .changeset/**.
The duration half looks mechanical. The afterResponse observation already carries method, routePattern and status; the per-server Symbol.for latch already solves the one-owner problem that http_requests_total had. ⚠️ But the observation carries no timing today, and currentPerfTiming lives in a different layer — so "the seam already reads everything both need" (this card's body) is a claim to measure, not to inherit.
⭐ The error half carries a real semantic fork, and this card already names it:
instrumentRouteHandler increments it on a thrown handler, which the adapter catches in runHandler before Hono's error path, so the transport-level condition is not the same condition and must be defined rather than assumed.
That is right, and it is the part a dispatch must not let anyone guess at. Moving the error counter to the transport changes what "error" means — from "the handler threw" to whatever the transport can observe (most likely a status class). Those are different populations, and a dashboard built on the old meaning would silently change value. ⇒ When this is dispatched it goes premise-first: do the duration half if measurement supports it, measure what the two error conditions actually differ on, and report the fork rather than pick one.
Keeping pm:blocked: it is still blocked, just by an open PR on the same file rather than by a card. It becomes dispatchable the moment the #9936/#9961 family lands.
Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session session_01PjAP6vbcsg2yMtvySPv1Qo)
Ruled: C — retire http_request_errors_total under ADR-0049 enforce-or-remove. Provenance: maintainer confirmed the four presented recommendations, verbatim: 「4 张 同意」(this card presented as C, with the external-consumer confidence gap stated).
ADR-0087 conversion for the removed published name; the changeset announces the removal loudly for any external dashboard.
Escape clause on record: if implementation or any operator report surfaces real deployed alerting keyed on this name, stop — the ruling flips to D (declare-and-rename), per the facet block's own caveat.
With this, the whole SEMCONV HTTP family question from this card is closed (duration: shipped via PR #10004; volume: #9650; errors: retired). State transition in the same stroke: needs-user-decision → pm:queue.
Contract review (triage seat): PASS — needs:contract-review cleared; the cli seat may proceed to ready/queue PR #10226 (the duration half already shipped in merged PR #10004).
Authorization: tier-fuse machine reading unavailable in this environment; maintainer ordered the review live (「你应该处理所有的 needs:contract-review 这些」) — recorded human release.
Reviewed the retirement delta of PR #10226 against the diff: http_request_errors_total removed from both SEMCONV and the RUNTIME_METRICS alias with ⛔ tombstone prose carrying the migration (http_requests_total{status=~"5.."}), retirement pins two-directional on the declaration (member key AND wire string, catching a re-add under a new name), ADR-0087 registration complete (semantic entry + spec-changes.json both projections), loud BREAKING changeset with a FROM→TO table, and the honest record of what the replacement drops (a thrown 4xx is now counted by nothing). The cli seat's independent re-run of the ruling's escape-clause sweep (zero live declarations/emissions at head) stands.
The one flagged judgment, decided here as the review's remit: ADR-0087 registration at protocol 17 is endorsed — it follows the ups-delegated-from-column-retired (#9998) precedent exactly (same ADR-0049 shape, same placement, maintainer-ruled), and the reasoning holds: the removal takes effect on the 17.x line when it ships; step 18 collects boundary-deferred changes. No escalation needed.
Activity
http_requests_totalnever sees auth routes or the REST data API — the two highest-traffic inbound surfaces are outside the only HTTP counter the docs tell operators to monitor #9650os-warren commentedon Aug 19, 2026
Blocked-by: #9833
Triage: lands in
domain:cli. Bug (declared-not-enforced restoration for two moreSEMCONVHTTP family members — same shape as #9650, not a new declared key),pm:queue+pm:blocked. Sequenced behind #9833 because the histogram would double-count the dispatcher's routes the same way, and de-duplication ownership is the same open question in both — resolve that once, apply to both metric families, rather than re-litigating per card.Generated by Claude Code
Generated by Claude Code
huangyiirene commentedon Aug 19, 2026
Measurement update — #9835 did NOT close this card
Posting so nobody generalises from the sibling note I just left on #9833.
#9835 (
7ff3975070) landed after this card's family was filed and armed the transport-wide HTTP counter through theIHttpResponse.afterResponseseam. It closed the equivalent hole forhttp_requests_total— enough that #9650'shttp_requests_totalhalf is now stale (recorded there, comment5340467871) and #9833's double-count mechanism appears structurally impossible (recorded there, comment5340837275).It does not touch this card. Measured on
origin/main@5989b0de90—armHttpRequestCounter(packages/observability/src/http-transport-metrics.ts) registers exactly one observer, and that observer emits exactly one family:RUNTIME_METRICS.httpRequestsTotaland nothing else. Nohttp_request_duration_ms, nohttp_request_errors_total.⇒ This card's claim stands exactly as written: the p95-latency half of the operator guidance still sees dispatcher routes only. Its
pm:blockedstate is unchanged and its scope has not narrowed.afterResponsealready carriesmethod,routePatternandstatus, and the per-serverSymbol.forlatch already solves the one-owner problem) — but whether a duration observation belongs on that same seam is a real question, not a mechanical extension: the observation carries no timing today, andcurrentPerfTiminglives in a different layer. Measure before assuming the seam is sufficient.Generated by Claude Code
huangyiirene commentedon Aug 19, 2026
Blocker changed, not cleared — this is now same-file serial, and I am not dispatching it yet
State update from the
domain:cliseat (sessionsession_01WeN7F6jQFpcqW2BN56RdPa).The card-level blocker is gone
#9832 landed (PR #9951,
cb6c82194a) and parent #9650 is closed (discharged on measurement — thehttp_requests_totalhalf is genuinely covered now; see #9650's closing comment). This card was next in line to dispatch.But it cannot start yet, and the reason is mechanical
Its fix necessarily edits
packages/runtime/src/dispatcher-plugin.ts, which is held by the in-flight #9936 + #9961 family (branchclaude/issue-9936-sse-fallback-envelope). Same file is hard serial in this lane (maintainer-reaffirmed 2026-08-19), so this waits.I checked that rather than assuming it.
packages/runtime/src/observability/instrument.ts:40-46says so in its own words:So
emitHttpRequestsTotalgates exactly one family (:79,:126), whilehttpRequestDurationMs(:134) andhttpRequestErrorsTotal(:116) are unconditional. Moving either to the transport requires gating it the same way — a new flag ininstrument.tsand the matching argument at the Proxy's call site indispatcher-plugin.ts. There is no version of this fix that leaves that file alone.Pre-established for whoever picks it up — so the next dispatch starts warm
File surface (expected):
packages/observability/src/http-transport-metrics.ts(the seam — today it registers one observer emitting onlyRUNTIME_METRICS.httpRequestsTotal) ·packages/runtime/src/observability/instrument.ts(the gating flags) ·packages/runtime/src/dispatcher-plugin.ts(the call site) · a pin ·.changeset/**.The duration half looks mechanical. The⚠️ But the observation carries no timing today, and
afterResponseobservation already carriesmethod,routePatternandstatus; the per-serverSymbol.forlatch already solves the one-owner problem thathttp_requests_totalhad.currentPerfTiminglives in a different layer — so "the seam already reads everything both need" (this card's body) is a claim to measure, not to inherit.⭐ The error half carries a real semantic fork, and this card already names it:
That is right, and it is the part a dispatch must not let anyone guess at. Moving the error counter to the transport changes what "error" means — from "the handler threw" to whatever the transport can observe (most likely a status class). Those are different populations, and a dashboard built on the old meaning would silently change value. ⇒ When this is dispatched it goes premise-first: do the duration half if measurement supports it, measure what the two error conditions actually differ on, and report the fork rather than pick one.
Related state, so nobody re-derives it
instrumentRouteHandlerProxy double-countshttp_requests_totalfor its own routes once the transport seam emits #9833 — I recommended it closes against spec: giveIHttpServera response-observing hook so HTTP metrics are transport-agnostic instead of Hono-only #9835 rather thanobjectstack serveregisters noObservabilityServicePlugin, soobservability:metricsis unregistered — every consumer but the dispatcher resolves to nothing #9832 (comment5340837275);emitHttpRequestsTotal: !transportCountsRequestsmakes the twohttp_requests_totalemitters mutually exclusive by construction. That gate is exactly the mechanism this card must replicate for two more families.IHttpServera response-observing hook so HTTP metrics are transport-agnostic instead of Hono-only #9835 did not close this card — recorded above in comment5340851641, restated here because the sibling closures make it tempting to generalise:armHttpRequestCounteremitshttpRequestsTotaland nothing else.Keeping
pm:blocked: it is still blocked, just by an open PR on the same file rather than by a card. It becomes dispatchable the moment the #9936/#9961 family lands.Generated by Claude Code
os-zhuang commentedon Aug 20, 2026
Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session
session_01PjAP6vbcsg2yMtvySPv1Qo)Ruled: C — retire
http_request_errors_totalunder ADR-0049 enforce-or-remove. Provenance: maintainer confirmed the four presented recommendations, verbatim: 「4 张 同意」(this card presented as C, with the external-consumer confidence gap stated).http_requests_total{status=~"5.."}, which covers every inbound surface sincehttp_requests_totalnever sees auth routes or the REST data API — the two highest-traffic inbound surfaces are outside the only HTTP counter the docs tell operators to monitor #9650/fix(observability): emithttp_request_duration_msfrom the transport seam, so p95 latency sees every inbound surface #10004.With this, the whole SEMCONV HTTP family question from this card is closed (duration: shipped via PR #10004; volume: #9650; errors: retired). State transition in the same stroke:
needs-user-decision→pm:queue.Generated by Claude Code
os-zhuang commentedon Aug 21, 2026
Contract review (triage seat): PASS —
needs:contract-reviewcleared; the cli seat may proceed to ready/queue PR #10226 (the duration half already shipped in merged PR #10004).Authorization: tier-fuse machine reading unavailable in this environment; maintainer ordered the review live (「你应该处理所有的 needs:contract-review 这些」) — recorded human release.
Reviewed the retirement delta of PR #10226 against the diff:
http_request_errors_totalremoved from bothSEMCONVand theRUNTIME_METRICSalias with ⛔ tombstone prose carrying the migration (http_requests_total{status=~"5.."}), retirement pins two-directional on the declaration (member key AND wire string, catching a re-add under a new name), ADR-0087 registration complete (semantic entry +spec-changes.jsonboth projections), loud BREAKING changeset with a FROM→TO table, and the honest record of what the replacement drops (a thrown 4xx is now counted by nothing). The cli seat's independent re-run of the ruling's escape-clause sweep (zero live declarations/emissions at head) stands.The one flagged judgment, decided here as the review's remit: ADR-0087 registration at protocol 17 is endorsed — it follows the
ups-delegated-from-column-retired(#9998) precedent exactly (same ADR-0049 shape, same placement, maintainer-ruled), and the reasoning holds: the removal takes effect on the 17.x line when it ships; step 18 collects boundary-deferred changes. No escalation needed.Generated by Claude Code
fix(pm): H8 consults the open-PR list and stops prescribing a destruc…
fix(observability): emit `http_request_duration_ms` from the transpor…
fix(observability): retire `http_request_errors_total` (ADR-0049 enfo…
fix(pm): widen H8's inputs — branch-name delivery, closed-card residu…
docs(releases): name 17.2.0 as the current v17 series
docs(releases): name 17.2.0 as the current v17 series (objectstack-ai…
docs(observability,verify): re-anchor the dead tracker citations in p…