Skip to content

fix(sink,tui): restore session init callback, trace ID was never shown - #861

Open
maoueh wants to merge 1 commit into
developfrom
feature/trace-id-not-shown-on-backprocessing
Open

fix(sink,tui): restore session init callback, trace ID was never shown#861
maoueh wants to merge 1 commit into
developfrom
feature/trace-id-not-shown-on-backprocessing

Conversation

@maoueh

@maoueh maoueh commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Problem

substreams run never displayed the Trace ID while backprocessing, sitting on Connecting... indefinitely:

Connecting...

  Longest-running jobs:
    [Stage: 0, Range: 25652000-25653000, Duration: 68s]

Root cause

Commit 7908100 ("Feature/request v3", #690, first released in v1.17.0, 2025-10-21) touched sink/types.go in two hunks only: it added the v3 import and retyped the interface method.

-	HandleSessionInit(ctx context.Context, req *pbsubstreamsrpc.Request, ...)
+	HandleSessionInit(ctx context.Context, req *pbsubstreamsrpcv3.Request, ...)

The fullSinkerHandlers struct field, its HandleSessionInit method and both NewSinkerFullHandlers* constructor signatures were left on rpc/v2 in that same file. The concrete type stopped satisfying its own interface, so the sinker's handler.(SinkerSessionInitHandler) assertion silently stopped matching and the session callback has never been invoked since. It compiled cleanly the whole time because nothing asserted the two agreed.

Two consequences, one cause:

  • TUI mode — the template gates the trace ID line and the whole per-stage progress section on .Connected; the Longest-running jobs block sits outside that gate, which is exactly why the reported output showed running jobs under a Connecting... header.
  • Non-TUI modes (--output json, jsonl, ...) — the TraceID:, Server HEAD block: and blocks-to-process summary lines silently stopped printing.

A second, independent bug sat behind the first: the bubbletea model matched *rpc.Response_Session while the UI sends a *rpc.SessionInit. Even a firing callback would not have updated the view. Both had to go.

Changes

  • Align the session-init chain on v3. The interface is where the API deliberately moved; the constructors are the half left behind. The only field that moved is req.Modulesreq.Package.Modules.
  • Fix the model's message type so the session actually reaches the view.
  • Fix SinkerErrorHandler, dead for the identical reason (*rpc.Error vs error). Wired into the TUI so a retried stream falls back to Connecting... and picks up the new trace ID, instead of advertising a stale one.
  • Compile-time interface assertions on fullSinkerHandlers for all five optional interfaces. These hooks are reached by type assertion, so signature drift turns them into dead code with no compiler complaint — this is what surfaced the session-init bug within seconds of being added.
  • Guard barmode against a divide by zero. Bar mode (m) was unreachable while the connected view never rendered; BarSize is 0 until the first tea.WindowSizeMsg, so pressing m in that window collapsed the entire view into a template error.

Breaking change

handleSessionInit and handleError passed to NewSinkerFullHandlers / NewSinkerFullHandlersWithPartial change type.

Callers passing nil are unaffected. Callers passing a callback get a compile error pointing at the exact line — and have had a silently dead callback since v1.17.0, so no behavior can depend on it today. Callers implementing SinkerSessionInitHandler directly on their own type already wrote the v3 signature and are unaffected; moving the interface back to v2 instead would have broken those consumers silently, which is why the fix goes this direction.

I could not verify how the external sinks (substreams-sink-sql, -kv, -files, ...) call these constructors — separate repos. The compile error will surface it at their next bump.

Verification

go build ./... and the full go test ./... suite pass. Each fix has a test that was confirmed to fail without it:

  • TestModelUpdate_SessionInit — reproduces the exact stuck-on-Connecting... output.
  • TestModelUpdate_ReconnectRefreshesTraceID — stale trace ID across a retry.
  • TestModelView_ConnectedBarModeEdgeCases — confirmed integer divide by zero without the guard.
  • TestNewSinkerFullHandlers_SatisfiesOptionalInterfaces / TestFullSinkerHandlers_ForwardsSessionInitAndError — asserts on what the constructors actually return, since the compile-time assertions only cover the concrete type.

The "Feature/request v3" change (7908100, released in v1.17.0) retyped
`SinkerSessionInitHandler.HandleSessionInit` to take a `rpc/v3.Request`
but left `fullSinkerHandlers` and both `NewSinkerFullHandlers*`
constructors on `rpc/v2`. The concrete type stopped satisfying its own
interface, so the sinker's `handler.(SinkerSessionInitHandler)` assertion
silently stopped matching and the session callback was never invoked.

Consequences, all from that single root cause:

- `substreams run` in TUI mode stayed on `Connecting...` forever, never
  displaying the trace ID, and hid the whole per-stage progress section.
- `substreams run` in non-TUI modes stopped printing the `TraceID:`,
  `Server HEAD block:` and blocks-to-process summary lines.

A second, independent bug sat behind the first: the bubbletea model
matched `*rpc.Response_Session` while the UI sends a `*rpc.SessionInit`,
so even a firing callback would not have updated the view.

`SinkerErrorHandler` was dead for the same reason (`*rpc.Error` vs
`error`). Fixing it lets the TUI fall back to `Connecting...` and pick up
the new trace ID when the sinker retries a severed stream, instead of
advertising a stale one.

Since these hooks are reached through a type assertion, a signature that
drifts turns them into dead code with no compile error. Added
compile-time interface assertions on `fullSinkerHandlers` to close that
hole permanently — they are what surfaced the session-init bug.

Also guards `barmode` against a divide by zero: bar mode was unreachable
while the connected view never rendered, and `BarSize` is 0 until the
first `tea.WindowSizeMsg` arrives.

BREAKING: the `handleSessionInit` and `handleError` callbacks passed to
`NewSinkerFullHandlers`/`NewSinkerFullHandlersWithPartial` change type.
Callers passing nil are unaffected; callers passing a callback get a
compile error and have had a dead callback since v1.17.0 regardless.
@maoueh
maoueh requested a review from sduchesneau August 1, 2026 05:41
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