Skip to content

NewRelic.Telemetry.Absinthe.handle_event/4 crashes with {:badmap, nil} on subscription registration #561

@prtngn

Description

@prtngn

Describe the bug

NewRelic.Telemetry.Absinthe.handle_event/4 crashes with {:badmap, nil} whenever an Absinthe subscription registration runs through the pipeline. The handler is then auto-detached by :telemetry, which silently disables Absinthe transaction tracing for the rest of the OS process lifetime.

The issue is the assumption on lib/new_relic/telemetry/absinthe.ex:85:

def handle_event(@operation_stop, meas, meta, _config) do
  operation = apply(Absinthe.Blueprint, :current_operation, [meta.blueprint])
  span_name = operation_span_name(meta.blueprint.execution.result.emitter)
  ...

Absinthe.Phase.Telemetry emits [:absinthe, :execute, :operation, :stop] for all operations — queries, mutations, and the initial subscription registration. For subscriptions, Absinthe.Phase.Subscription.SubscribeSelf replaces the resolution phase with Absinthe.Phase.Subscription.Result, so blueprint.execution.result is nil (the subscription has not actually executed any field — it only registered a topic). The .emitter access then raises BadMapError.

Subsequent ongoing subscription publish events use a different telemetry event ([:absinthe, :subscription, :publish, :stop]), which the agent does not subscribe to — so this bug only surfaces at subscribe time, but it permanently disables operation tracing because :telemetry detaches the handler on the first error.

Stacktrace (production)

Handler {:new_relic, :absinthe} has failed and has been detached.
Class=:error
Reason={:badmap, nil}
Stacktrace=[
  {NewRelic.Telemetry.Absinthe, :handle_event, 4,
   [file: ~c"lib/new_relic/telemetry/absinthe.ex", line: 85]},
  {:telemetry, :do_execute, 4,
   [file: ~c"deps/telemetry/src/telemetry.erl", line: 202]},
  {Absinthe.Phase.Telemetry, :do_run, 3,
   [file: ~c"lib/absinthe/phase/telemetry.ex", line: 73]},
  {Absinthe.Pipeline, :run_phase, 3,
   [file: ~c"lib/absinthe/pipeline.ex", line: 411]},
  {Absinthe.GraphqlWS.Transport, :run, 4,
   [file: ~c"lib/absinthe/graphql_ws/transport.ex", line: 249]},
  {Absinthe.GraphqlWS.Transport, :run_doc, 5,
   [file: ~c"lib/absinthe/graphql_ws/transport.ex", line: 217]},
  {Bandit.WebSocket.Connection, :handle_frame, 3,
   [file: ~c"lib/bandit/websocket/connection.ex", line: 66]},
  {Bandit.WebSocket.Handler, :pop_frame, 3,
   [file: ~c"lib/bandit/websocket/handler.ex", line: 50]}
]

Repro outline

Any Absinthe schema with a subscription field + any client that drives a subscription document through Absinthe.Pipeline.run/2. We hit it via absinthe_graphql_ws over Bandit websockets — first incoming subscribe message reliably triggers it. A mix test ExUnit case calling Absinthe.run/2 with a subscription document on a schema attached to a Phoenix.PubSub should also reproduce.

Suggested fix

The crash is at the access; pattern-matching the operation type before reaching for .emitter is enough. For example:

def handle_event(@operation_stop, meas, meta, _config) do
  operation = apply(Absinthe.Blueprint, :current_operation, [meta.blueprint])

  case operation do
    %{type: :subscription} ->
      # Subscription registration emits :execute :operation :stop too,
      # but execution.result is nil at this point (no field was
      # resolved — only a topic was registered). Skip span emission;
      # ongoing publishes are covered by :absinthe.subscription.publish.*.
      :ok

    _ ->
      span_name = operation_span_name(meta.blueprint.execution.result.emitter)
      transaction_name = transaction_name(meta.blueprint.schema, operation)
      # ... existing body ...
  end
end

Happy to send a PR if that direction is acceptable.

Environment

  • Elixir & Erlang version: Elixir 1.19.5, Erlang/OTP 28.5
  • Agent version: new_relic_agent 1.40.2 (latest at the time of writing)
  • absinthe 1.10.2, absinthe_graphql_ws 0.3.6, absinthe_phoenix 2.0.5
  • phoenix 1.8.7, bandit 1.11.0, telemetry 1.4.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions