The container-listing section still described the removed `ls -1` parse,
`pkg/queue`, and the old `[]container.PathStat` return. Update it to the current
`find -print0` + errgroup fan-out returning `ContainerDirListing{Files, Failures,
Truncated}`. Also remove the stale `WithBlock`/`DefaultDialTimeout` paragraph left
above the non-blocking `grpc.NewClient` test — its own next paragraph already
contradicted it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three defects on the shutdown and container-listing paths:
- Telemetry drain ran the langfuse and otel sinks serially under one deadline.
langfuse's ForceFlush/Shutdown ignore the caller ctx and block on their own
timeout, so a langfuse outage burned the whole budget and the healthy otel
sink's final batch was never flushed. Add observer.Drain, which flushes then
shuts down each sink in its own goroutine bounded by the deadline; main uses
it, retiring the inline flush-then-shutdown dance.
- The partial-success listing put the raw stat/list error into the 200 body's
Failures[].Message, exposing docker-layer detail (container ids, the daemon
address) that response.Error only reveals under develop mode. Send a generic
message to the client and keep the raw text dev-gated, logged server-side.
- A path read successfully by one query could also appear in Failures when
another query failed on it, order-dependent. Reconcile at assembly: drop any
failure whose path was read successfully, so no path is in both arrays.
Also bound the exec stdout buffered before parsing so a compromised sandbox
can't stream unbounded output into memory.
Tests: Drain delivers a healthy otel sink while a langfuse sink blocks and
returns within its deadline; the listing failure message is dev-gated; a path
never lands in both Files and Failures. Live: unreachable collector -> shutdown
stays bounded (~5s) and exits cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The graceful-shutdown branch only logged "cleaning up resources" and exited
without flushing telemetry, so the last buffered batch was dropped on every
SIGTERM. Drain it, with three corrections:
- main: flush then shut down the observer on shutdown, bounded by a 5s
deadline in a goroutine so an unreachable collector can't stall exit.
Flush must precede Shutdown because the langfuse observer's Shutdown
cancels its context without flushing; its final batch drains only via
ForceFlush while the context is still live.
- observer Flush/Shutdown: drain both the langfuse and otel sinks and join
their errors instead of returning after the first, which skipped the
second sink entirely when both were configured.
- telemetryClient Flush/Shutdown: attempt all three providers and always
close the grpc connection, joining errors, so a stalled provider flush
no longer skips the rest or leaks the connection.
Tests: the langfuse observer test proves Shutdown drops the buffered batch
while ForceFlush drains it to the sink; observer-level tests prove both
sinks drain and errors aggregate. Live: with an unreachable collector,
shutdown stays bounded (~5s) and exits cleanly instead of stalling ~10s.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The startup dial used grpc.WithBlock with a 10s timeout, so a set-but-
unreachable collector stalled main() for up to 10s, and once that dial failed
the client was nil forever — a collector that came up later never connected
without a restart. Switch to grpc.NewClient (non-blocking): startup never
stalls and gRPC re-establishes the connection in the background on its own.
Also build all three exporters before any provider, so the exporter-error
cleanup (closing the connection) happens before any batch/reader goroutine
starts and can't leak one. Drops the deprecated WithBlock/WithReturnConnectionError.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Ignore the local `backend/vendor/` tree (187 MB, present only for the
vendored-fork build) so it can't be accidentally staged.
- Remove the architecture-table row for `pkg/queue/`, which was deleted when
the container stat fan-out moved to errgroup.
- Drop the internal "H4" report-id labels from the telemetry test — the
sentences already say what they guard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a fake OTLP collector so NewTelemetryClient can be exercised against a
reachable endpoint: it builds all three providers and both ForceFlush and
Shutdown export and tear down without error. Previously only the
not-configured and unreachable-dial paths were covered.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Unit coverage for NewTelemetryClient: an unset OTEL_HOST yields ErrNotConfigured,
and an unreachable collector returns within the dial timeout instead of hanging
(a TCP-accepting, silent listener drives the WithBlock path).
Also close the grpc.ClientConn on the exporter-creation error paths — a
successful dial followed by a failed exporter New() previously leaked it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A set-but-unreachable OTEL collector hung startup: NewTelemetryClient dialed
with grpc.WithBlock() and no timeout on the deadline-free signal context, so
the process blocked forever before it ever served. And a non-ErrNotConfigured
init error from either observability client was log.Fatalf, killing the
process. Both let an OPTIONAL integration take the whole app down.
Bound the dial with a 10s timeout, and on init failure degrade to a no-op
observer with a logged warning instead of exiting. Verified live: a bad
OTEL_HOST that hung startup indefinitely now boots in ~14s with a warning and
runs a full flow; a reachable collector still boots in ~4s with no warning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: add unit tests for langfuse context and noop observer
Add tests for observation context put/get round-trip, nested context
override behavior, and noop observer: trace ID generation, parent
inheritance, explicit override, Shutdown, ForceFlush, and enqueue.
* test: add edge case tests for noop observer branch coverage
Add two missing branch tests: explicit ObservationID with no parent
context (verifies new trace generation), and explicit TraceID with
parent (verifies observation ID is NOT inherited when trace is explicit).
Add comprehensive unit tests for all utility functions in the langfuse
helpers package: mergeMaps, ObservationLevel, GenerationUsageUnit,
GenerationUsage, ModelParameters, GetLangchainModelParameters, trace/span
ID generation, time helpers, and pointer helpers.