Observability and Cost
Trace lifecycle, persistence failures and cost estimates without treating missing telemetry as zero spend.
Tracing described here belongs to son-of-anton-review on feat/cloudflare-native, the deployed-engine lineage. The Cloudflare trace-reading API belongs to unmerged feat/greptile-operator-parity. The Node dashboard has a different trace contract. A working native engine does not imply that either dashboard API is deployed alongside it.
Traces are observations, not a complete ledger of attempts
src/trace-emitter.js creates an in-memory trace with a generated trace ID and spans. The general tracing switch is on unless GILF_TRACING is exactly 0 or false. The native container explicitly creates its injected tracer with enabled: true, and GILF_TRACING is not forwarded by the container allowlist. Setting that variable only on the Worker therefore does not disable native tracing.
Tracer methods guard their own errors and can disable further tracing. A flush is attempted once per tracer; a synchronous sink error is swallowed by the guard. This is best-effort telemetry, not proof that every accepted webhook or failed attempt produced a durable trace. A retry can create another trace ID for the same review key.
On the native path, the entrypoint owns the trace through publication. A later failure to flush the buffered trace to D1 can fail the review response even though the in-memory tracer itself does not throw.
Sources: engine src/trace-emitter.js:9-10,60-82,88-97,261-277; cloudflare-native/container/entrypoint.mjs:126-176,378-385,421-450; cloudflare-native/src/container-env.js:15-85.
Phase spans
| Span | Meaning when emitted |
|---|---|
agent.workflow | Root workflow |
repo.prepare | Repository preparation |
context.load | Preflight context assembly |
context.graph | Graph build within preflight |
chat.primary | Primary semantic model call |
validation.cf-sandbox, validation.e2b | Validation lanes named by provider |
publish | Native service publication stage |
http.model-price | OpenRouter price-check phase |
hypothesis.primary, hypothesis.shadow, chat.shadow | Optional runner paths |
There is no fixed seven-span contract for every attempt. Early failures, policy skips, durable-analysis recovery and optional execution paths produce different span sets. A shadow-lane error does not necessarily make the trace's final status error: the entrypoint can explicitly end a successfully completed review as ok while an individual shadow span failed.
Spans support timing, status, error, model, token counts, cost, attributes, events and optional input/output. On trace failure, unfinished spans become errors and the cause is copied to trace metadata. Missing usage and cost remain null unless some span reports a value; totals sum only reported values.
Sources: engine src/codex-review-runner.js:34-60,1112-1149; src/trace-emitter.js:18-39,111-173,186-229; cloudflare-native/container/entrypoint.mjs:95-155.
Storage and disclosure
Native traces map to D1 review_traces and review_trace_spans. The buffer replaces a snapshot with the same trace ID; the bridge restores drained snapshots when a flush fails. State is written before traces in the HTTP handler, so they are not an atomic snapshot across both operations.
The native row mapper can persist input_json, output_json, attributes, events and errors. It does not implement universal redaction. The parity API omits input/output fields from its returned DTO, and the parity UI has only Attributes and Events tabs. That omission is not proof that raw input/output can never exist in D1 or that arbitrary attributes are sanitized.
The Node trace store is selected through GILF_DB_PATH; without a trace store, the local trace-list API returns an empty list. It also returns an empty list on a caught trace read error, so empty results alone cannot establish absence of reviews.
Sources: engine cloudflare-native/src/trace-buffer.js:32-54,62-118; cloudflare-native/src/d1-store-adapter.js:303-350; cloudflare-native/src/state-transport.js:558-564; server.mjs:27-35; src/operator-dashboard.js:291-313; parity cloudflare/src/review-trace-queries.js:14-64; UI src/components/TraceObservability.tsx:321-388.
Model cost
estimateProviderCostUsd uses input and output token counts and USD-per-million-token rates. A model-specific GILF_MODEL_PRICES entry wins over the built-in provider table. OpenAI and Anthropic have static code tables; OpenRouter and Codex tables are empty. These are configured estimates, not live price verification or invoices.
The checked-in native configuration supplies this model-price entry:
{"z-ai/glm-5.3-flash":{"input":0.075,"output":0.25}}No usage or no resolved price returns null. With a price present, missing/non-numeric token fields or rate components are coerced to zero by the arithmetic. A displayed zero therefore deserves inspection of the inputs; it is not automatically proof of a free call.
Sources: engine src/model-provider.js:44-93; cloudflare-native/wrangler.jsonc:154-161.
Validation cost
The code uses costModel: estimate:wall-clock, calculated as seconds multiplied by CPU, memory and disk rates. It does not query provider billing.
| Executor | CPU USD/vCPU-second | Memory USD/GiB-second | Disk USD/GB-second | Fallback shape |
|---|---|---|---|---|
managed-cf-sandbox | 0.000020 | 0.0000025 | 0.00000007 | 2 vCPU, 8192 MB, 16 GB disk |
managed-e2b | 0.000014 | 0.0000045 | 0 | 2 vCPU, 512 MB, 0 disk contribution |
GILF_VALIDATION_COST_RATES merges partial executor rate overrides over the built-ins; malformed JSON falls back. Actual reported CPU/memory values can replace the fallback shape. The estimate excludes unrelated orchestration, Worker, database, storage and network charges.
Missing or invalid elapsed input normally prevents a rate estimate, but explicit null values undergo JavaScript numeric coercion. validationSpanAttributes can likewise convert explicit null costUsd or seconds to zero. Do not promise that all unknown values are perfectly represented as null.
Source: engine src/validation-executor.js:146-245.
Reading averages and budgets
The Node-only /operator/api/trace-stats computes means over traces reporting each metric and excludes root spans from phase/type breakdowns. Nested and concurrent spans still overlap, so summed phase duration need not equal wall-clock duration.
The parity Worker instead exposes /operator/api/traces and /operator/api/traces/:id. Its dashboard computes PR averages from up to five recent trace details, not all historical runs.
The E2B budget gate sums all recorded validation.e2b costs, without date, repository or role filtering. Missing costs and concurrent work make it unsuitable as a hard billing cap. The parity Test Lab must read the same data and budget setting for its display to agree with the native gate.
Sources: engine src/operator-dashboard.js:326-402; parity cloudflare/src/review-state-api.js:876-899; UI src/lib/api.ts:149-158; engine cloudflare-native/src/container-env.js:100-141.
The implemented observability path is the custom tracer and stores. No Langfuse or OpenTelemetry exporter was found in the audited engine source and root package manifest. See Cost and traces for the operator workflow.