Skip to content

trunk-merge/pr-3478/5fe47493-2526-4e60-9434-dc736114dd1c#3770

Closed
trunk-io[bot] wants to merge 21 commits into
mainfrom
trunk-merge/pr-3478/5fe47493-2526-4e60-9434-dc736114dd1c
Closed

trunk-merge/pr-3478/5fe47493-2526-4e60-9434-dc736114dd1c#3770
trunk-io[bot] wants to merge 21 commits into
mainfrom
trunk-merge/pr-3478/5fe47493-2526-4e60-9434-dc736114dd1c

Conversation

@trunk-io

@trunk-io trunk-io Bot commented Jul 23, 2026

Copy link
Copy Markdown
Trunk Merge Pull Request Banner

This pull request was created and is being managed by Trunk Merge.

This pull request is based on the main branch at SHA c9605b504f6c9b4ddaab102fc245fd4f5c17827f.

See more details here.

When CI completes, this pull request will be closed automatically.

Pull Requests Being Tested

This pull request is testing the changes from pull request 3478.

tatoalo and others added 21 commits July 23, 2026 17:14
…OTLP

Adds OtelRunTelemetry, a SessionLogWriter sink that ships an allowlisted
metadata subset of the session log (run/turn/tool lifecycle, usage, errors;
never message content or tool arguments) to PostHog Logs, and, when a traces
URL is configured, builds one APM trace per run (task_run root span, a turn
span per prompt, a tool_call:<kind> span per tool call) with trace/span ids
stamped on log records so Logs and APM cross-link.

Resource attributes carry run_id/task_id/team_id/user_id/distinct_id so cloud
runs are filterable per user in the Logs UI. Configured via
POSTHOG_AGENT_OTEL_LOGS_URL/_TOKEN (+ optional POSTHOG_AGENT_OTEL_TRACES_URL),
deliberately not standard OTEL_* names so OTel SDKs in user code running in
the sandbox never auto-export into the telemetry project. Telemetry stays off
unless the logs pair is set, flushes on session cleanup/terminal errors, and
can never break session log persistence (sink failures are isolated).

Replaces the unwired otel-log-writer whose default endpoint
(/i/v1/agent-logs) does not exist in the ingest service, and removes the dead
AgentConfig.otelTransport field.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
…emetry

Addresses three review findings:

- The sandbox never emits task_complete for successful runs (the terminal
  "completed" status is decided by the workflow outside), so the root span
  was ending with unset status on success. The trace builder now marks the
  root span OK when a turn ends cleanly with end_turn; a run error still
  always wins and a turn completion arriving after an error cannot flip the
  status back.
- reportFatalError (uncaught exception / unhandled rejection) marked the run
  failed via the API but never touched telemetry, leaving hard crashes
  invisible in the new pipeline. It now mirrors an error record
  (error_source=agent_server_crash) and shuts telemetry down, ending the root
  span as errored and flushing before the process dies.
- The README "Writing logs" section still described the removed
  otel-log-writer as the preferred full-transcript path to the nonexistent
  /i/v1/agent-logs endpoint; rewritten to match the sink-based metadata
  export.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
…est turn

Addresses two external review findings:

- _posthog/console records are no longer exported to PostHog Logs. Console
  lines are free-text agent-server diagnostics that interpolate arbitrary
  data - the user-message handler logs a 100-char prompt preview and the
  extension-notification handler stringifies params into that channel - so
  exporting them verbatim violated the metadata-only allowlist. They remain
  in the S3 session log and the event-ingest stream. Regression tests assert
  console entries (including an interpolated prompt preview) never map to an
  exported record.

- The task_run root span status is now resolved at shutdown from the LATEST
  turn outcome instead of being set sticky-OK on the first clean end_turn.
  A multi-turn run whose final turn is cancelled/refused no longer reports
  OK; a run error still always wins. The root span only exports at end, so
  per-turn status writes were cosmetic anyway.

Stacked on posthog-code/agent-run-otel-telemetry (pure fast-forward).

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
… fixes

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Addresses four review findings on the run telemetry export:

- _posthog/error records and the root span no longer carry the raw error
  string: it is free text that can embed prompt or repo content (exception
  paths, provider errors). Exported signal is now the generic "run error"
  body plus error_source/stop_reason attributes; the full message stays in
  the session log and the task run's error_message.
- Telemetry flush/shutdown use Promise.allSettled so logs and traces
  complete independently, and both batch processors cap exports at 5s
  (exportTimeoutMillis) instead of the SDK's 30s default - a rejecting or
  hanging traces endpoint can no longer starve log delivery or delay
  session cleanup.
- A run error now closes still-open tool spans as ERROR with
  tool_status=interrupted, so APM never shows a healthy-looking active
  tool under a failed run.
- OtelTransportConfig/AgentConfig.otelTransport are restored as
  @deprecated ignored stubs: @posthog/agent is published, so removing
  exported types is an API break reserved for a major.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
The task detail routes redirected away on any 404 from the detail query,
even when a cached or list copy of the task exists. Optimistic and
cloud-pending tasks are not returnable by the API yet (see the route
loader comments), so the unconditional redirect kicked users off tasks
they had just created.

The detail query still always runs (so a stale cached copy converges on
the server's latest run state), but a 404/missing result only triggers
the redirect when there is no usable cached task to render.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Local verification of the telemetry branch showed logs and the turn span
landing in ClickHouse but never the task_run root span, even after the run
reached completed and extra delay. Root cause: the root span only ended in
cleanupSession, which is only reachable via SIGTERM or the close command -
and sandbox teardown never delivers SIGTERM to agent-server, because it is
an exec'd process (docker stop signals only the container's PID 1; Modal
terminate is immediate). The root span was still open when the process was
killed, so it never exported. The turn span survived only because it ended
mid-run and the 2s batch exporter got it out.

Fix: end telemetry eagerly at the run's in-process terminal points instead
of relying on teardown:

- finalizeRunTelemetry: full telemetry shutdown (ends the root span with
  the resolved status and drains both exporters) when a background run's
  initial or resume prompt settles - the run is over in-sandbox at that
  point; the workflow marks the terminal status and destroys the sandbox
  right after.
- signalTaskComplete: upgrade the terminal-failure flush to a full
  shutdown, after the error mirror is appended, so failed runs export a
  root span with ERROR status.
- cleanupSession remains the path for interactive close.

Known limitation, now documented: an interactive session ended by hard
teardown (e.g. inactivity timeout) loses the root span; its turn/tool
spans and logs still assemble under the same trace id.

Tests: order assertion that the error mirror lands before the terminal
shutdown, and a parameterized check that finalizeRunTelemetry fires for
background runs only. The full agent-server + telemetry suites pass (161).

Stacked on posthog-code/agent-run-otel-telemetry (pure fast-forward).

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Rebasing the terminal-state hardening onto main's evolved sessionService
adopted main's cloudHydrationPromises in-flight memoization in place of the
original hydration-key set. Keying that map by run id alone reintroduced the
bug the original fix addressed: a run going terminal while a resume-chain
(or single-run) hydration was still in flight would dedupe onto the pending
promise and never apply the final transcript. The map is now keyed by
run id + hydration mode (terminal-chain / resume-chain / single).

Also updates the two terminal-hydration host tests to main's
getTaskRunSessionLogsResult client API (result-object shape, and the
ancestor+current parallel fetch that makes each resume hydration two calls).

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Mirrors the PostHog/posthog report update: documents the incorporated
scout run log mirror (#71094) alongside the OTLP run-telemetry export,
including the two pipelines' differing privacy postures and scopes.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Mirrors the PostHog/posthog report update: the scout run log mirror
gained a direct OTLP delivery leg (TASK_RUN_LOGS_MIRROR_OTLP_URL/_TOKEN)
for hosts whose stdout no collector tails - in local dev append_log runs
in the host Django process, which the dev collector (docker containers
only) never sees.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Review finding: cloudHydrationPromises memoizes terminal-chain and
resume-chain hydrations under different mode keys, so both can be in
flight for the same run. If the run settles while a resume-chain
hydration's fetches are pending, the terminal-chain hydration records
the full chain as processed - and the late resume-chain write then
clobbered processedLineCount with its smaller leaf-only cursor
(updateSession is a blind assign with no monotonic guard).

Impact was bounded (the transcript content is identical and the next
terminal hydration self-heals), but it violated the invariant the
mode-keying fix set out to guarantee: the terminal transcript is
authoritative. Re-read the session at write time and floor a
non-terminal cursor at the settled value once cloudStatus is terminal.

The regression test resolves the resume-chain fetches after the run
settles and asserts the settled cursor survives; verified to fail
without the fix.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
…state

Findings from a 4-angle simplify pass (reuse / simplification /
efficiency / altitude), no behavior change except where noted:

- Extract resolveEffectiveCloudStatus: the 9-line terminal-wins status
  precedence chain was duplicated byte-for-byte in deriveCloudRunState
  and deriveSessionViewState; three review angles flagged it
  independently. One exported helper in cloudRunState.ts now owns the
  policy.
- isTaskDetailNotFoundError uses requestErrorStatus(error) === 404
  instead of string-matching "[404]" in the message - the fetcher keeps
  that message format only as legacy, and the substring check could
  false-positive on a non-404 error quoting an upstream body. Behavior
  tightened accordingly (plain Errors mentioning [404] no longer read as
  404s).
- taskDetailQuery no longer retries 404s: the detail fetch is now
  always-on, and optimistic/cloud-pending tasks 404 by design, so
  react-query's default 3 retries turned every mount of a fresh task
  into 4 requests.
- pickFreshestTask is binary (all six call sites were), which also
  deletes the logically dead ?? fallbacks at the two hook call sites.
- sessionService: merge the duplicated optimistic-clear blocks and
  skip-apply early returns in performCloudTaskSessionHydration; shrink
  finalizeTerminalCloudTask to the (taskRunId, status) both call sites
  use; clearTerminalCloudPromptState derives taskId from the session
  (removing the dual-key lookup hazard) and now also serves the
  pre-existing inline terminal cleanup in handleCloudTaskUpdate, so the
  composer-settling invariant lives in one place; staleness is computed
  once per update in handleCloudTaskUpdate.
- OTel modules: normalizeMethod (the safety-relevant __posthog/
  double-prefix rule), asString, and the batch flush-interval constant
  move to otel-attributes.ts instead of being duplicated across
  otel-telemetry.ts and otel-trace-builder.ts.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
REPORT.md was a working artifact for the agent session, not part of the
change itself.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
…g it

The sandbox delivers POSTHOG_AGENT_OTEL_LOGS_TOKEN via the container
environment, which agent-spawned tool subprocesses inherit - so any `env`
dump in a persisted transcript or PR body would leak the key (flagged by
review bots on PostHog/posthog#71100).

bin.ts is the only consumer: parse it into the server config, then delete
it from process.env so children never see it. Defense in depth, not a
boundary - same-UID processes can still read the container's initial env
via /proc; the key itself remains a write-only capture token scoped to a
dedicated telemetry project.

No test: a one-line env mutation at the CLI entry with no cheap seam;
exercising it would mean spawning the CLI end to end.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Main's #3744 (avoid redundant terminal history fetches) made a terminal
resume-chain hydration issue one whole-chain fetch instead of an
ancestor+current pair, so the expected call count after the terminal
watch drops from 4 to 3. The regression the test pins is unchanged: the
terminal hydration must start its own fetch rather than dedupe onto the
in-flight resume-chain hydration (count would stay 2 if mode-keying
broke).

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
@github-actions

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit c152236.

@trunk-io trunk-io Bot closed this Jul 23, 2026
@trunk-io
trunk-io Bot deleted the trunk-merge/pr-3478/5fe47493-2526-4e60-9434-dc736114dd1c branch July 23, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant