Problem
finish_telemetry() records rich attributes on the lifecycle span but never sets the span's
status and never records the exception (lib/python/base_cli/integrations.py:127-162). It calls
set_attribute for base_cli.outcome, base_cli.status, base_cli.exit_code, and
base_cli.duration_ms, adds a base_cli.run.finished event, then end.
An OpenTelemetry span whose status is never set stays UNSET. Every observability backend —
Jaeger, Tempo, Honeycomb, Datadog, Cloud Trace — drives its error rates, error-highlighting, and
alerting off span status, not off arbitrary attributes. So a base-cli CLI that fails produces a span
that looks successful in every standard view. For a framework whose stated audience is
operations/SRE, and whose whole value proposition is a consistent operational contract, that
inverts the most important signal.
span.record_exception() is likewise never called, so failed runs carry no exception type,
message, or stack in the trace even though outcome_from_exception() has already classified the
failure precisely.
Verified evidence
Reviewed 2026-09-30 at a58ec109349fa3f3d03eae5b0de078b39ea361a2.
$ grep -n "set_status\|record_exception\|StatusCode" lib/python/base_cli/integrations.py
(no matches)
The available outcome data is unambiguous — InvocationOutcome.kind is already one of success,
usage_error, nonzero_return, interrupted, aborted, click_error, system_exit,
unexpected_error — so the mapping to StatusCode.OK / StatusCode.ERROR is mechanical.
Proposal
- Set span status from the outcome:
StatusCode.OK for success, StatusCode.ERROR otherwise,
with description set to the outcome kind. Keep the existing attributes.
- Record the exception when one is available, using
span.record_exception().
- Route both through the existing
_safe_span_call() helper so a missing method on a minimal
test tracer stays non-fatal, and import StatusCode lazily inside the try like trace already is.
- Decide deliberately whether a user interrupt (
SIGINT / Ctrl+C) is an error span. Either
choice is defensible; record it. Treating a deliberate cancellation as ERROR generates alert
noise, so UNSET or OK with an attribute may be better.
- Document the status mapping in
docs/integrations.md so adopters can build dashboards on it.
Acceptance criteria
- A failing command produces a span with
StatusCode.ERROR; a succeeding command produces OK.
- An unexpected exception is attached to the span via
record_exception().
- The interrupt policy is documented and tested.
- A tracer that implements only
start_span/end still works (existing best-effort guarantee holds).
- Ordinary exporter failures still cannot change the command's exit code.
Non-goals
- Do not make OpenTelemetry a required dependency.
- Do not add metrics or log-signal export in this issue.
Problem
finish_telemetry()records rich attributes on the lifecycle span but never sets the span'sstatus and never records the exception (
lib/python/base_cli/integrations.py:127-162). It callsset_attributeforbase_cli.outcome,base_cli.status,base_cli.exit_code, andbase_cli.duration_ms, adds abase_cli.run.finishedevent, thenend.An OpenTelemetry span whose status is never set stays
UNSET. Every observability backend —Jaeger, Tempo, Honeycomb, Datadog, Cloud Trace — drives its error rates, error-highlighting, and
alerting off span status, not off arbitrary attributes. So a base-cli CLI that fails produces a span
that looks successful in every standard view. For a framework whose stated audience is
operations/SRE, and whose whole value proposition is a consistent operational contract, that
inverts the most important signal.
span.record_exception()is likewise never called, so failed runs carry no exception type,message, or stack in the trace even though
outcome_from_exception()has already classified thefailure precisely.
Verified evidence
Reviewed 2026-09-30 at
a58ec109349fa3f3d03eae5b0de078b39ea361a2.The available outcome data is unambiguous —
InvocationOutcome.kindis already one ofsuccess,usage_error,nonzero_return,interrupted,aborted,click_error,system_exit,unexpected_error— so the mapping toStatusCode.OK/StatusCode.ERRORis mechanical.Proposal
StatusCode.OKforsuccess,StatusCode.ERRORotherwise,with
descriptionset to the outcome kind. Keep the existing attributes.span.record_exception()._safe_span_call()helper so a missing method on a minimaltest tracer stays non-fatal, and import
StatusCodelazily inside thetryliketracealready is.SIGINT/Ctrl+C) is an error span. Eitherchoice is defensible; record it. Treating a deliberate cancellation as
ERRORgenerates alertnoise, so
UNSETorOKwith an attribute may be better.docs/integrations.mdso adopters can build dashboards on it.Acceptance criteria
StatusCode.ERROR; a succeeding command producesOK.record_exception().start_span/endstill works (existing best-effort guarantee holds).Non-goals