part of: ENG-3645 - flush OTEL spans on SIGTERM/SIGINT so killed OpenCode runs keep child spans - #3
Merged
Yanir-D merged 1 commit intoJul 21, 2026
Conversation
…child spans OpenTelemetry batches spans and only exported them on the graceful session.idle / server.instance.disposed lifecycle events. Every investigation readwrite is killed (pod soft-timeout SIGTERM or client-disconnect cancellation) before either event fires, so the batch died with the process and every killed run silently lost all of its child generation/span observations. Register once-guarded SIGTERM/SIGINT handlers that forceFlush() the batch then shut the exporter down within the OPENCODE_KILL_GRACE_S window the pod already reserves after SIGTERM. Route the existing server.instance.disposed event through the same guarded routine so shutdown runs exactly once regardless of trigger. Deliberately does not call process.exit(): OpenCode owns the process and still needs the grace window to flush its own stdout NDJSON (the source of the salvaged diff); the pod's post-grace SIGKILL remains the terminator. part of: ENG-3645 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Yanir-D
deleted the
feature/eng-3645-opencode-readwrite-spans-are-silently-lost-on-every-killed
branch
July 21, 2026 12:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Register once-guarded
SIGTERM/SIGINThandlers thatforceFlush()the batched OTEL spans and shut the exporter down before the OpenCode process is torn down — so a killed readwrite still exports its child generation/span observations. Also routes the existingserver.instance.disposedevent through the same guarded routine so shutdown happens exactly once regardless of trigger.This is PART A of ENG-3645 — the ticket's own "actual span-loss fix".
Why
OpenTelemetry batches spans and (before this change) only exported them on the graceful
session.idle/server.instance.disposedlifecycle events. Every investigationcreate_pull_requestreadwrite is killed before either fires:SIGTERMto the process group (opencode.py_signal_process_tree_sigterm), or_kill_process_tree(killpg(SIGTERM)→ grace →SIGKILL).Neither path emits an OpenCode lifecycle event, so
forceFlush()was never called and the whole batch died with the process. Result today:create_pull_requestspans with zero child observations on every killed run (baselineinv-fc6f1ae7, traces5ba603cc/3843ca06, children=0).The pod already reserves an
OPENCODE_KILL_GRACE_S(10s) window afterSIGTERM(ENG-3512) — used to salvage OpenCode's stdout NDJSON. This change makes the plugin flush its span batch inside that same window.Design notes
process.exit()— deliberate. OpenCode owns the process and still needs the grace window to flush its own stdout NDJSON (the source of the salvaged diff); exiting here would truncate it. The pod's post-graceSIGKILLremains the terminator.server.instance.disposedshares the same guard.Tests
bun test— 17 pass (5 new): handler registration, flush-then-shutdown, once-guard across repeated triggers, no-op when credentials are missing, and the no-process.exitcontract. Full gate green:typecheck,lint,format:check,build.Deploy chain (this PR alone ships nothing)
The plugin is pinned by commit in the code-intel image build (
discovery-codeservices/tree-sitter-service/Dockerfile,git checkout <sha>). After this merges:discovery-codeCI rebuilds + pushestree-sitter-service(automatic).discovery-code-helm-chart→ sync → pod redeploy (automatic).Acceptance (ENG-3645 #1) — provable post-deploy only
The span-flush effect is observable only on a killed run. Post-deploy: trigger a
create_pull_requestreadwrite that times out / is cancelled and confirm the investigation trace now shows child GENERATION/SPAN observation(s) under thecreate_pull_requestspan (model, prompt/output sizes,timed_out/resolution) — where today there are zero.part of: ENG-3645
🤖 Generated with Claude Code