Skip to content

test(ci): type-check and run the custom-observability probes (#867) - #874

Merged
rogeriochaves merged 3 commits into
mainfrom
tech-debt-fixer-867
Aug 11, 2026
Merged

test(ci): type-check and run the custom-observability probes (#867)#874
rogeriochaves merged 3 commits into
mainfrom
tech-debt-fixer-867

Conversation

@langwatch-agent

Copy link
Copy Markdown
Contributor

Ticket

Closes #867. javascript/examples/custom-observability had no type check and no CI test invocation, so its four probes ran only by hand.

Change

A tsconfig matching the openai-realtime-demo example, a typecheck script, and a CI step running pnpm -F custom-observability-example test:all.

The step carries no fork guard, unlike the Test (Examples) step it sits above. Those probes run in-process against an InMemorySpanExporter with no network and no LLM, so a fork PR that breaks the auto-init contract should go red rather than skip. AC4 is satisfied in its stronger form: no key is required, so no guard is needed.

Two things the type check found the moment it was switched on

1. The example was a major version behind the library. It pinned @opentelemetry/sdk-trace-base: ^1.30.0 (resolving 1.30.1) while the library depends on 2.7.1, so it handed a 1.x SimpleSpanProcessor to a 2.x setupScenarioTracing. It worked at runtime and did not typecheck:

test-scenario-only.ts(43,20): error TS2322: Type 'SimpleSpanProcessor' is not assignable to type 'SpanProcessor'.

The example is documentation, so it now pins exactly what the library pins rather than being cast past. This is the drift the issue anticipated ("the risk is not that they are broken; it is that nothing would tell us when they break") arriving on the first run.

2. test-no-auto-init proved nothing at all. This is the probe the issue singles out as guarding "a regression that has shipped before".

It compared trace.getTracerProvider().constructor.name before and after the import. That call returns the same ProxyTracerProvider instance whether or not a provider has been registered, so both reads are the string "ProxyTracerProvider" forever and the comparison can never be unequal. Measured:

identity: same object each call? true
before: ProxyTracerProvider
after:  ProxyTracerProvider     (after new NodeTracerProvider().register())
names differ? false

So with a provider force-registered on import, the old probe exited 0 and printed PASS. Wiring that into CI as-is would have bought false confidence, which is worse than the status quo because it reads as coverage.

Registration swaps the proxy's delegate, NoopTracerProvider to a real provider, so the probe now reads the delegate. It also asserts the delegate starts as NoopTracerProvider, so it cannot go vacuous from the other end either: if anything registers before the comparison, it fails loudly instead of comparing two constants again.

Evidence, per acceptance criterion

AC1 — type-checked in CI. pnpm typecheck:all now reaches it (examples/custom-observability typecheck: Done). Annotating a tracer as number in test-scenario-only.ts:

examples/custom-observability typecheck: test-scenario-only.ts(48,7): error TS2322: Type 'Tracer' is not assignable to type 'number'.

Reverting returns it green.

AC2 — probes run in CI. New Test (Custom observability probes) step. All four print PASS locally; the run URL will be on this PR's checks.

AC3 — they fail loudly on a regression. Registering a NodeTracerProvider after the import:

old probe this PR
clean exit 0, PASS exit 0, PASS
auto-init forced on import exit 0, PASS exit 1, Provider changed from NoopTracerProvider to NodeTracerProvider

AC4 — no API key required. All four pass with OPENAI_API_KEY and LANGWATCH_API_KEY unset, hence no fork guard on the step.

typecheck:all, lint:all and lint:lib clean. The 1079 library tests are unchanged.

Notes

The issue describes the lint half as "Fixed in #865", but #865 is still open, so main has no lint script for this package either. Nothing here depends on it: this adds typecheck and the CI step only. If #865 lands first the two will conflict on the scripts block of one small package.json.

Also noticed and left alone: with-config-file/scenario.config.mjs names an individual in a comment, which the house style rules out. Flagged on the issue rather than folded into a CI-wiring diff.

Advisory note

Advisory PR from the tech-debt-fixer bot, for human review, not to be merged by the bot.

The package's four probes ran only when someone ran them by hand. It has no
typecheck script, so `pnpm typecheck:all` skipped it silently, and
javascript-ci's example step runs `pnpm -F vitest-examples test`, which does not
reach it.

It gets a tsconfig matching the openai-realtime-demo example, a typecheck
script, and its own CI step. The step carries no fork guard, unlike the example
step below it: the probes run in-process against an InMemorySpanExporter with no
network and no LLM, so a fork PR that breaks the auto-init contract should go
red rather than skip.

Turning the type check on surfaced a real drift the ticket predicted but did not
name. The example pinned @opentelemetry/sdk-trace-base ^1.30.0 while the library
depends on 2.7.1, so it handed a 1.x SimpleSpanProcessor to a 2.x
setupScenarioTracing. It worked at runtime and did not typecheck. The example is
documentation, so it now pins what the library pins.

The bigger find is that test-no-auto-init proved nothing. It compared
`trace.getTracerProvider().constructor.name` before and after the import, but
that call returns the same ProxyTracerProvider instance whether or not anything
is registered, so both reads are the string "ProxyTracerProvider" forever. The
probe the issue describes as guarding a regression that has shipped before could
not observe that regression: with a provider force-registered on import it still
printed PASS and exited 0. Registration swaps the proxy's delegate, so the probe
now reads the delegate, and asserts the delegate starts as NoopTracerProvider so
it cannot go vacuous again from the other end.

Evidence per acceptance criterion:
- AC1: annotating a tracer as `number` in test-scenario-only.ts turns
  typecheck:all red; reverting returns it green.
- AC3: registering a NodeTracerProvider on import exits 1 with
  "Provider changed from NoopTracerProvider to NodeTracerProvider". The same
  mutation against the old probe exited 0 and printed PASS.
- AC4: all four probes pass with OPENAI_API_KEY and LANGWATCH_API_KEY unset.

typecheck:all, lint:all and lint:lib clean; 1079 library tests unchanged.

Closes #867

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@langwatch-agent langwatch-agent added the tech-debt-fixer Opened by the tech-debt-fixer agent (shared langwatch-agent bot identity) label Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The custom observability example now has TypeScript checking, pinned OpenTelemetry dependencies, stronger probe assertions, exporter filter tests, and JavaScript CI coverage.

Changes

Custom observability validation

Layer / File(s) Summary
Example package type-checking and dependencies
javascript/examples/custom-observability/package.json, javascript/examples/custom-observability/tsconfig.json
The package adds a typecheck script and strict TypeScript configuration. OpenTelemetry SDK versions are pinned to 2.7.1, with required type packages in devDependencies.
Observability probe assertions
javascript/examples/custom-observability/test-no-auto-init.ts, javascript/examples/custom-observability/test-scenario-only.ts
The probes validate the initial OpenTelemetry delegate, scenario execution, collected spans, and scenarioOnly scope filtering.
Exporter filter coverage
javascript/src/tracing/__tests__/filters.test.ts
Tests capture spans forwarded by LangWatchTraceExporter and verify scenarioOnly and withCustomScopes filtering.
Probe suite CI execution
.github/workflows/javascript-ci.yml
JavaScript CI runs the package’s test:all suite without secrets or fork restrictions.

Suggested reviewers: rogeriochaves

Poem

A rabbit checks the tracer’s state,
TypeScript guards the gate.
Probes test spans, scopes, and flow,
CI watches them all grow.
No secret hides the trail.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes adding CI execution and type checking for the custom-observability probes.
Description check ✅ Passed The description explains the CI, type-checking, dependency, probe, and regression-detection changes in the pull request.
Linked Issues check ✅ Passed The changes satisfy #867 by adding type checking, CI probe execution, regression detection, and key-free test operation.
Out of Scope Changes check ✅ Passed The tracing filter tests support the observability probe objectives and do not introduce unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tech-debt-fixer-867

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
javascript/examples/custom-observability/test-no-auto-init.ts (2)

30-54: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Compare delegate identity, not only class names.

delegateName() discards the delegate object. The assertion at Line 54 compares strings. If the import replaces one NoopTracerProvider with another provider of the same class, the probe reports success although the delegate changed.

Retain the delegate objects for equality checks. Derive constructor.name only for logs and failure messages. Verify that the locked OpenTelemetry implementation returns a stable delegate object.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@javascript/examples/custom-observability/test-no-auto-init.ts` around lines
30 - 54, Update the provider probe around delegateName so it retains the actual
delegate objects before and after importing `@langwatch/scenario`, using
constructor.name only for logging and failure messages. Compare delegate
identity rather than class-name strings, and verify the locked OpenTelemetry
implementation returns the same delegate object when no provider is registered.

24-28: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Guard getDelegate() before calling it.

getDelegate() is not on the public TracerProvider type. The runtime truthiness check can also call a non-function value. Add an explicit type guard such as typeof provider.getDelegate === 'function' before calling it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@javascript/examples/custom-observability/test-no-auto-init.ts` around lines
24 - 28, Update delegateName so the getDelegate member is invoked only when
typeof provider.getDelegate is "function"; otherwise use provider directly.
Preserve the existing constructor-name return behavior while preventing calls to
non-function values.

Source: Coding guidelines

javascript/examples/custom-observability/tsconfig.json (1)

19-23: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Check scenario.config.mjs type-checking

tsconfig.json includes **/*.mjs, so javascript/examples/custom-observability/with-config-file/scenario.config.mjs is part of the tsc --noEmit contract, but semantic JavaScript checking only runs when checkJs: true is set. Enable checkJs, or remove the .mjs include if the config file is runtime-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@javascript/examples/custom-observability/tsconfig.json` around lines 19 - 23,
Update the custom-observability tsconfig so its treatment of JavaScript matches
the intended contract: enable checkJs for semantic checking of the included
scenario.config.mjs file, or remove the **/*.mjs include if that file is
runtime-only. Keep the existing TypeScript include behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@javascript/examples/custom-observability/test-no-auto-init.ts`:
- Line 34: Add a workspace-aware lint script for the
custom-observability-example package, or update the root parallel lint command
to exclude that package and openai-realtime-demo. Ensure pnpm lint:all completes
successfully so the subsequent probe step runs.

---

Nitpick comments:
In `@javascript/examples/custom-observability/test-no-auto-init.ts`:
- Around line 30-54: Update the provider probe around delegateName so it retains
the actual delegate objects before and after importing `@langwatch/scenario`,
using constructor.name only for logging and failure messages. Compare delegate
identity rather than class-name strings, and verify the locked OpenTelemetry
implementation returns the same delegate object when no provider is registered.
- Around line 24-28: Update delegateName so the getDelegate member is invoked
only when typeof provider.getDelegate is "function"; otherwise use provider
directly. Preserve the existing constructor-name return behavior while
preventing calls to non-function values.

In `@javascript/examples/custom-observability/tsconfig.json`:
- Around line 19-23: Update the custom-observability tsconfig so its treatment
of JavaScript matches the intended contract: enable checkJs for semantic
checking of the included scenario.config.mjs file, or remove the **/*.mjs
include if that file is runtime-only. Keep the existing TypeScript include
behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e67b8267-0829-46c0-81a5-ac1a420a1d8b

📥 Commits

Reviewing files that changed from the base of the PR and between 88aec40 and b1a940c.

⛔ Files ignored due to path filters (1)
  • javascript/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • .github/workflows/javascript-ci.yml
  • javascript/examples/custom-observability/package.json
  • javascript/examples/custom-observability/test-no-auto-init.ts
  • javascript/examples/custom-observability/tsconfig.json

Comment thread javascript/examples/custom-observability/test-no-auto-init.ts
Comment thread javascript/examples/custom-observability/package.json

@langwatch-agent langwatch-agent left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 P1 — a newly required probe exits successfully without asserting its claimed behavior; see inline comment.

@langwatch-agent
langwatch-agent removed the request for review from rogeriochaves August 4, 2026 06:34
@langwatch-agent langwatch-agent self-assigned this Aug 4, 2026
@langwatch-agent langwatch-agent added the hound-checked Triaged by the pr-hound agent at the current head SHA label Aug 5, 2026
… it probes breaks

test-scenario-only.ts printed PASS and exited 0 unconditionally, so wiring
test:all into CI made a non-asserting script a required gate: a broken run() or
tracing setup would still have gone green. The other three probes already exit
non-zero on their own failure conditions; this one did not.

Turn its observations into gates: the scenario must succeed, scenario spans must
exist, and both deliberately-created noise spans must reach the collector, since
otherwise "no noise was exported" would be true for a reason unrelated to
filtering.

The exporter is not reachable from the probe, so the filtering claim is bound
where it can be observed: scenarioOnly's selected scope must be the scope the
scenario spans are actually emitted under, and must not also select the noise
scope. Renaming the instrumentation scope would otherwise turn the filter into a
drop-everything rule with nothing to catch it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@javascript/examples/custom-observability/test-scenario-only.ts`:
- Around line 152-182: The assertions in the scenarioOnly validation currently
inspect raw InMemorySpanExporter data rather than post-filtered spans. Update
test-scenario-only.ts to import and construct the exporter used after
scenarioOnly filtering, then assert its output contains the expected scenario
spans and no http-server noise spans. Keep memoryExporter.getFinishedSpans()
only for detecting collector span loss, and update the nearby checks around
scenarioOnlyScopes, unselected, and noiseSpans accordingly.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e401d56f-4f31-40ff-9072-0e136878addc

📥 Commits

Reviewing files that changed from the base of the PR and between b1a940c and fa76d1a.

📒 Files selected for processing (1)
  • javascript/examples/custom-observability/test-scenario-only.ts

Comment thread javascript/examples/custom-observability/test-scenario-only.ts
…their shape

filters.test.ts asserted the rule objects deep-equal a literal. Every one of
those assertions still passes if the exporter ignores `filters` entirely, so
nothing showed that a noise span is dropped, which is the only thing the filters
exist to do.

LangWatchTraceExporter applies its filters in export() before delegating to the
OTLP exporter it extends, so spying on the parent makes the drop observable with
no network call and no live endpoint. Two cases: scenarioOnly forwards the
scenario scope and drops http-server and next.js, and withCustomScopes forwards
the named scope alongside it.

Mutation-checked: inverting scenarioOnly's include to exclude, and widening it to
admit the noise scope, each fail two of the new cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
javascript/src/tracing/__tests__/filters.test.ts (1)

1-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the required file-level test documentation.

javascript/src/tracing/__tests__/filters.test.ts currently documents the helper contracts but does not document the test command, coverage expectation, mocked dependencies, or example filter cases. Add this as the first block, before the imports.

Use javascript/package.json:

  • pnpm -F @langwatch/scenario test or pnpm -F @langwatch/scenario test:ci for coverage.
  • Document vitest and the mocked parent exporter dependency.
  • Document an example filter case, such as including @langwatch/scenario with scenarioOnly.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@javascript/src/tracing/__tests__/filters.test.ts` around lines 1 - 4, Add a
file-level documentation block before the imports in filters.test.ts describing
the test commands from javascript/package.json, including the coverage command,
the Vitest framework, the mocked parent exporter dependency, and an example
filter case such as including `@langwatch/scenario` via scenarioOnly.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@javascript/src/tracing/__tests__/filters.test.ts`:
- Around line 1-4: Add a file-level documentation block before the imports in
filters.test.ts describing the test commands from javascript/package.json,
including the coverage command, the Vitest framework, the mocked parent exporter
dependency, and an example filter case such as including `@langwatch/scenario` via
scenarioOnly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 13e19bed-887a-46d7-bc6f-848ed37be0fe

📥 Commits

Reviewing files that changed from the base of the PR and between fa76d1a and 7171c38.

📒 Files selected for processing (1)
  • javascript/src/tracing/__tests__/filters.test.ts

@langwatch-agent langwatch-agent added the ci-green Latest run of every check is passing (checks API, not the legacy commit-status index) label Aug 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Automated low-risk assessment

This PR was evaluated against the repository's Low-Risk Pull Requests procedure and does not qualify as low risk.

This PR modifies files in restricted directories that require manual review per policy.

This PR requires a manual review before merging.

@langwatch-agent
langwatch-agent requested review from sergioestebance and removed request for rogeriochaves August 11, 2026 18:23
@rogeriochaves
rogeriochaves requested review from drewdrewthis and removed request for sergioestebance August 11, 2026 18:25
@rogeriochaves
rogeriochaves merged commit 3506baa into main Aug 11, 2026
22 checks passed
@rogeriochaves
rogeriochaves deleted the tech-debt-fixer-867 branch August 11, 2026 18:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-green Latest run of every check is passing (checks API, not the legacy commit-status index) hound-checked Triaged by the pr-hound agent at the current head SHA tech-debt-fixer Opened by the tech-debt-fixer agent (shared langwatch-agent bot identity)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(ci): examples/custom-observability is type-checked and tested by nothing in CI

2 participants