Skip to content

Add diagnostics_channel TracingChannel support - #30

Closed
MiichelleZhou wants to merge 7 commits into
eval/3650/basefrom
eval/3650/qodo/round-6
Closed

MiichelleZhou wants to merge 7 commits into
eval/3650/basefrom
eval/3650/qodo/round-6

Conversation

@MiichelleZhou

@MiichelleZhou MiichelleZhou commented Sep 28, 2026 •

Copy link
Copy Markdown

This PR introduces tracing channels support to pg-pool and pg querying methods as discussed in #3619

I haven't yet run a benchmark, but without active tracing or subscribers the overhead is non-existent so existing users shouldn't be affected. The overhead would come from the consumers of the diag channels published here which is done anyways via monkey patching.

I added the following channels:

Channel Type Package Description
pg:query TracingChannel pg Query lifecycle (start, end, error with async context)
pg:connection TracingChannel pg Client connection lifecycle
pg:pool:connect TracingChannel pg-pool Pool connection acquisition lifecycle
pg:pool:release Channel pg-pool Client released back to pool
pg:pool:remove Channel pg-pool Client removed from pool

I understand there may be hesitation given that AI tooling was used in the implementation. To be clear, this isn't a drive-by contribution. I designed the channel layout, wrote the proposal document, and iterated through multiple rounds to handle real edge cases (Node 18 compat, preserving custom Promise types, hasSubscribers guards). The AI assisted with the code, but the approach and decisions are mine.

Usage

Instrumentations will only need to subscribe to tracing channels to create traces, logs or metrics:

import { tracingChannel } from 'diagnostics_channel'

tracingChannel('pg:query').subscribe({
  start({ query, client }) {
    // start span
    // query: { text, name, rowMode }
    // client: { database, host, port, user, processID, ssl }
  },
  asyncEnd({ query, result }) {
    // end span
    // result: { rowCount, command }
  },
  error({ query, error }) {
    // record error on span
  },
})

This is part of a broader initiative to bring TracingChannel support to the most widely used Node.js database and cache libraries. The same pattern has already been merged and shipped in:

I have been directly involved in those prior implementations and I'm happy to own this through to release, address any feedback, and provide whatever support is needed. Would love to hear your thoughts.

Supersedes #3624.


Note

Medium Risk
Instrumentation wraps connect, query, and pool checkout/release hot paths; behavior should be unchanged without subscribers, but callback wrapping adds complexity in core client/pool flows.

Overview
Adds Node diagnostics_channel instrumentation so APM and custom observers can subscribe without monkey-patching pg or pg-pool.

pg exposes pg:query and pg:connection as TracingChannels around Client.query (callback-based queries) and Client.connect, with context for query text, connection metadata, and result rowCount/command. pg-pool adds pg:pool:connect (TracingChannel with pool stats and client reuse), plus pg:pool:release and pg:pool:remove publish channels on release and removal.

Shared diagnostics.js helpers load the built-in module when available, fall back safely in non-Node environments, and use shouldTrace so work is skipped when there are no subscribers—while still tracing on Node 18 where hasSubscribers is missing. Unit tests cover channel payloads and skip unstable TracingChannel behavior on older Node versions.

Reviewed by Cursor Bugbot for commit b922f0d. Configure here.

logaretm and others added 7 commits March 5, 2026 12:40
Enables instrumentation libraries (OpenTelemetry, etc.) to subscribe to
structured events without monkey-patching. Uses TracingChannel for async
context propagation and plain channels for simple events.

Channels:
- pg:query (TracingChannel) — query lifecycle with result enrichment
- pg:connection (TracingChannel) — client connect lifecycle
- pg:pool:connect (TracingChannel) — pool checkout lifecycle
- pg:pool:release (plain) — client released back to pool
- pg:pool:remove (plain) — client removed from pool

All instrumentation is guarded by hasSubscribers for zero overhead when
unused. Gracefully degrades to no-ops on Node < 19.9 or non-Node
environments.

Closes #3619

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TracingChannel is not available on Node 18 LTS. Skip the tracing-dependent
tests gracefully instead of failing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Node 18 backported TracingChannel but without the aggregated
`hasSubscribers` getter (it returns `undefined` instead of a boolean).
Raw truthiness checks treat `undefined` as "no subscribers" which
silently disables tracing on Node 18.

Replace all `channel.hasSubscribers` guards with `shouldTrace(channel)`
which checks `hasSubscribers !== false` — treating `undefined` (Node 18)
as "might have subscribers, trace unconditionally" and `false` (Node 20+)
as "definitely no subscribers, skip".

Also removes the now-unnecessary test skip logic since TracingChannel
does exist on Node 18.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Node 18 backported TracingChannel but with a buggy implementation —
unsubscribing and resubscribing to the same channel crashes internally
(`_subscribers` becomes undefined). Node 16 has no TracingChannel at all.

Gate tests on `hasStableTracingChannel` which checks both that
`dc.tracingChannel` exists AND that the aggregated `hasSubscribers`
getter returns a boolean (only true on Node 19.9+/20.5+).

TracingChannel tests: skipped on Node 16/18, run on Node 20+
Plain channel tests (release/remove): run on all versions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…omise type

tracePromise wraps the result in a native Promise, which breaks
clients configured with a custom Promise implementation (e.g. bluebird).
Switch to traceCallback inside the user's this._Promise constructor
so the returned promise type is always correct.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@MiichelleZhou

Copy link
Copy Markdown
Author

/review

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add diagnostics channels for pg queries and pool lifecycles

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Expose query, connection, and pool lifecycle events to instrumentation without monkey-patching.
• Include query results, connection details, pool state, and errors in diagnostic contexts.
• Guard inactive channels and test callback, promise, and pool event paths.
Diagram

graph TD
  Pool["pg-pool"] --> PoolDiag["Pool diagnostics"] --> DC["diagnostics_channel"] --> Subscribers["Instrumentation subscribers"]
  Pool --> Client["pg Client"] --> ClientDiag["Client diagnostics"] --> DC
Loading
High-Level Assessment

Direct, subscriber-guarded diagnostics channels fit the goal of exposing asynchronous tracing without monkey-patching. Existing pool events do not provide tracing context, while external wrappers would need to intercept client and query callbacks.

Files changed (6) +557 / -11

Enhancement (4) +155 / -11
diagnostics.jsDefine optional pool diagnostics channels +35/-0

Define optional pool diagnostics channels

• Creates a tracing channel for pool acquisition and plain channels for release and removal. Falls back to inert channels when diagnostics are unavailable and accounts for Node 18's missing aggregate subscriber getter.

packages/pg-pool/diagnostics.js

index.jsInstrument pool acquisition, release, and removal +34/-0

Instrument pool acquisition, release, and removal

• Traces acquisition callbacks with pool counts and acquired-client details. Publishes release and removal events with client process IDs, including release errors when present.

packages/pg-pool/index.js

client.jsTrace client connections and queued queries +55/-11

Trace client connections and queued queries

• Wraps callback and promise connection paths with connection tracing when subscribed. Traces queries as they enter the queue and enriches their context with client details and completed result metadata.

packages/pg/lib/client.js

diagnostics.jsDefine optional query and connection tracing channels +31/-0

Define optional query and connection tracing channels

• Creates the 'pg:query' and 'pg:connection' tracing channels with an inert fallback. Its subscriber guard preserves tracing on Node 18 runtimes where the aggregate getter is undefined.

packages/pg/lib/diagnostics.js

Tests (2) +402 / -0
diagnostics.jsTest pool diagnostics events +197/-0

Test pool diagnostics events

• Covers acquisition context and client enrichment, release payloads and errors, and removal events using mock clients. Skips TracingChannel tests when the runtime lacks the expected stable API.

packages/pg-pool/test/diagnostics.js

diagnostics-tests.jsTest query and connection tracing +205/-0

Test query and connection tracing

• Checks successful and failed query events, result and client context, promise queries, and callback-based connections. Skips tracing cases on runtimes without the expected stable TracingChannel API.

packages/pg/test/unit/client/diagnostics-tests.js

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (7) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Installed pools fail to load 🐞 Bug ≡ Correctness
Description
The new top-level require('./diagnostics') imports a file omitted by pg-pool's package files
allowlist. In an npm-installed package, both the CommonJS entry point and the ESM wrapper reach that
import and fail with MODULE_NOT_FOUND.
Code

packages/pg-pool/index.js[3]

+const { poolConnectChannel, poolReleaseChannel, poolRemoveChannel, shouldTrace } = require('./diagnostics')
Evidence
The added import is unconditional, while the published-file allowlist contains index.js and esm
but not diagnostics.js; the ESM wrapper imports that same index file.

packages/pg-pool/index.js[1-3]
packages/pg-pool/package.json[43-50]
packages/pg-pool/esm/index.mjs[1-5]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The pool entry point requires a new file that the package does not publish.
## Fix Focus Areas
- packages/pg-pool/index.js[1-3]
- packages/pg-pool/package.json[43-50]
## Recommended Fix
Add `diagnostics.js` to the package `files` allowlist and verify that the packed package loads through both entry points.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Timed-out queries leave open traces 🐞 Bug ◔ Observability
Description
Client.query() installs the traced callback after its timeout handler has captured the original
query.callback. When the timer fires, it calls that original callback and then replaces the traced
callback with a no-op, so subscribers see a start but no traced completion or error.
Code

packages/pg/lib/client.js[R726-729]

+      queryChannel.traceCallback(
+        (tracedCb) => {
+          query.callback = tracedCb
+          enqueue()
Evidence
The timeout retains and calls the pre-tracing callback, whereas the added branch later assigns
tracedCb to query.callback; the timeout then replaces that property before the scheduled error
handler can use it.

packages/pg/lib/client.js[650-664]
packages/pg/lib/client.js[721-735]
packages/pg/lib/query.js[111-116]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Query timeouts bypass the newly installed tracing callback.
## Fix Focus Areas
- packages/pg/lib/client.js[650-664]
- packages/pg/lib/client.js[721-735]
## Recommended Fix
Have the timeout path invoke the current traced callback exactly once before disabling later completion, while retaining timer cleanup and existing caller behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Pool connections lose trace context 🐞 Bug ≡ Correctness
Description
Pool.connect() uses traceCallback() only to assign response.callback, ending the traced
synchronous scope before acquisition begins. On a new-client checkout, newClient() and its nested
client.connect() run afterward, so a subscriber's pool-connect context is not active during
connection startup.
Code

packages/pg-pool/index.js[R218-221]

+      poolConnectChannel.traceCallback(
+        (tracedCb) => {
+          response.callback = tracedCb
+        },
Evidence
The traced function contains only an assignment; newClient() occurs after it returns and calls
client.connect(). Node documents that traceCallback() runs bound stores during its wrapped
function and restores them for the eventual callback, not for unrelated work after the wrapped
function returns.

packages/pg-pool/index.js[218-278]
packages/pg-pool/index.js[281-289]
packages/pg/lib/client.js[209-218]
🌐 traceCallback\(\) runs the given function with the start channel's bound stores and separately restores them around the callback.

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The pool trace wraps callback assignment rather than connection acquisition, losing context during nested connection startup.
## Fix Focus Areas
- packages/pg-pool/index.js[218-227]
- packages/pg-pool/index.js[229-278]
## Recommended Fix
Move the acquisition and queue-setup branches into the function passed to `traceCallback`, while continuing to use its wrapped callback for asynchronous completion and preserving `connect()`'s return value.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Event-based queries produce no trace 🐞 Bug ◔ Observability
Description
The new query tracing branch requires query.callback, excluding supplied Query objects that use
events instead of a callback. When such a query completes through its supported end event, it
never enters the added tracing branch.
Code

packages/pg/lib/client.js[709]

+    if (shouldTrace(queryChannel) && query.callback) {
Evidence
A supplied query object remains callback-less when no callback argument is given;
Query.handleReadyForQuery() emits end in that case, but the new condition takes the untraced
branch.

packages/pg/lib/client.js[625-637]
packages/pg/lib/client.js[709-739]
packages/pg/lib/query.js[111-143]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Queries supplied as event-based `Query` objects bypass the new diagnostics channel.
## Fix Focus Areas
- packages/pg/lib/client.js[625-637]
- packages/pg/lib/client.js[709-739]
- packages/pg/lib/query.js[111-143]
## Recommended Fix
Instrument callback-less `Query` objects through their completion and error events, preserving their existing event and unhandled-error behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (3)
5. Node 18 queries pay tracing costs 🐞 Bug ➹ Performance
Description
shouldTrace() treats Node 18's missing aggregate hasSubscribers getter as permission to trace
every operation. On Node 18 versions with TracingChannel, even with no subscribers, each
callback-based query and connection builds context and callback wrappers, and pool checkouts do the
same.
Code

packages/pg/lib/diagnostics.js[R27-28]

+function shouldTrace(channel) {
+  return channel.hasSubscribers !== false
Evidence
Both added helpers explicitly classify an undefined getter as subscribed, and the resulting branches
allocate query, connection, or pool context and wrappers. The PR's tests also identify the missing
aggregate getter on older tracing implementations.

packages/pg/lib/diagnostics.js[23-29]
packages/pg-pool/diagnostics.js[27-33]
packages/pg/lib/client.js[709-735]
packages/pg-pool/index.js[204-227]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The absent aggregate subscriber getter makes Node 18 enter every tracing branch without subscribers.
## Fix Focus Areas
- packages/pg/lib/diagnostics.js[23-29]
- packages/pg-pool/diagnostics.js[27-33]
## Recommended Fix
When the aggregate getter is unavailable, check the constituent tracing channels' subscriber flags instead of assuming subscribers exist; retain the noop-channel behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Rejected requests leave no trace 🐞 Bug ◔ Observability
Description
Both new tracing setups sit after existing early-return guards: Client.query() skips tracing for
an unqueryable or closing client, and Pool.connect() skips it after pool shutdown. When those
guards reject callback or promise requests, callers receive errors but subscribers receive no
corresponding start or error events.
Code

packages/pg/lib/client.js[R709-710]

+    if (shouldTrace(queryChannel) && query.callback) {
+      const context = {
Evidence
The client guards return before the added query tracing block, while the pool-ending guard returns
before pool tracing and even before promisify() creates its callback.

packages/pg/lib/client.js[685-710]
packages/pg-pool/index.js[195-227]
packages/pg/lib/query.js[111-116]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Early rejection paths return before the new tracing wrappers are installed.
## Fix Focus Areas
- packages/pg/lib/client.js[685-735]
- packages/pg-pool/index.js[195-227]
## Recommended Fix
Include the early failure paths within each operation's tracing setup so their existing callback or promise rejection also publishes a trace error.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Cancelled queries leave open traces 🐞 Bug ◔ Observability
Description
The added query wrapper starts a trace when it enqueues a query, but queued-query cancellation
removes that query without calling its traced callback. If a caller cancels a query while another is
active, subscribers retain a start event with no completion or error event.
Code

packages/pg/lib/client.js[R726-730]

+      queryChannel.traceCallback(
+        (tracedCb) => {
+          query.callback = tracedCb
+          enqueue()
+        },
Evidence
The changed branch starts tracing before enqueue; cancel() can subsequently splice that query from
the queue without invoking query.callback, which is where the trace's asynchronous completion
would be published.

packages/pg/lib/client.js[542-560]
packages/pg/lib/client.js[703-739]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Cancelling a queued query removes it without completing the trace begun at enqueue time.
## Fix Focus Areas
- packages/pg/lib/client.js[542-560]
- packages/pg/lib/client.js[703-739]
## Recommended Fix
Give queued queries a trace-completion path on cancellation, or defer starting their trace until execution begins, without changing the existing caller cancellation behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Web pages:
  +20 more
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 11/18, lines 568/200; both must reach the floor). Router rationale: This is a broad runtime instrumentation change across pg and pg-pool with multiple independent callback, async lifecycle, compatibility, and public diagnostics-channel behaviors that could hide several subtle defects.

Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread packages/pg-pool/index.js
@@ -1,5 +1,6 @@
'use strict'
const EventEmitter = require('events').EventEmitter
const { poolConnectChannel, poolReleaseChannel, poolRemoveChannel, shouldTrace } = require('./diagnostics')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Installed pools fail to load 🐞 Bug ≡ Correctness

The new top-level require('./diagnostics') imports a file omitted by pg-pool's package files
allowlist. In an npm-installed package, both the CommonJS entry point and the ESM wrapper reach that
import and fail with MODULE_NOT_FOUND.
Agent Prompt
## Issue description
The pool entry point requires a new file that the package does not publish.
## Fix Focus Areas
- packages/pg-pool/index.js[1-3]
- packages/pg-pool/package.json[43-50]
## Recommended Fix
Add `diagnostics.js` to the package `files` allowlist and verify that the packed package loads through both entry points.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread packages/pg/lib/client.js
Comment on lines +726 to +729
queryChannel.traceCallback(
(tracedCb) => {
query.callback = tracedCb
enqueue()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Timed-out queries leave open traces 🐞 Bug ◔ Observability

Client.query() installs the traced callback after its timeout handler has captured the original
query.callback. When the timer fires, it calls that original callback and then replaces the traced
callback with a no-op, so subscribers see a start but no traced completion or error.
Agent Prompt
## Issue description
Query timeouts bypass the newly installed tracing callback.
## Fix Focus Areas
- packages/pg/lib/client.js[650-664]
- packages/pg/lib/client.js[721-735]
## Recommended Fix
Have the timeout path invoke the current traced callback exactly once before disabling later completion, while retaining timer cleanup and existing caller behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread packages/pg-pool/index.js
Comment on lines +218 to +221
poolConnectChannel.traceCallback(
(tracedCb) => {
response.callback = tracedCb
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Pool connections lose trace context 🐞 Bug ≡ Correctness

Pool.connect() uses traceCallback() only to assign response.callback, ending the traced
synchronous scope before acquisition begins. On a new-client checkout, newClient() and its nested
client.connect() run afterward, so a subscriber's pool-connect context is not active during
connection startup.
Agent Prompt
## Issue description
The pool trace wraps callback assignment rather than connection acquisition, losing context during nested connection startup.
## Fix Focus Areas
- packages/pg-pool/index.js[218-227]
- packages/pg-pool/index.js[229-278]
## Recommended Fix
Move the acquisition and queue-setup branches into the function passed to `traceCallback`, while continuing to use its wrapped callback for asynchronous completion and preserving `connect()`'s return value.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread packages/pg/lib/client.js
this._pulseQueryQueue()
}

if (shouldTrace(queryChannel) && query.callback) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Event-based queries produce no trace 🐞 Bug ◔ Observability

The new query tracing branch requires query.callback, excluding supplied Query objects that use
events instead of a callback. When such a query completes through its supported end event, it
never enters the added tracing branch.
Agent Prompt
## Issue description
Queries supplied as event-based `Query` objects bypass the new diagnostics channel.
## Fix Focus Areas
- packages/pg/lib/client.js[625-637]
- packages/pg/lib/client.js[709-739]
- packages/pg/lib/query.js[111-143]
## Recommended Fix
Instrument callback-less `Query` objects through their completion and error events, preserving their existing event and unhandled-error behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +27 to +28
function shouldTrace(channel) {
return channel.hasSubscribers !== false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. Node 18 queries pay tracing costs 🐞 Bug ➹ Performance

shouldTrace() treats Node 18's missing aggregate hasSubscribers getter as permission to trace
every operation. On Node 18 versions with TracingChannel, even with no subscribers, each
callback-based query and connection builds context and callback wrappers, and pool checkouts do the
same.
Agent Prompt
## Issue description
The absent aggregate subscriber getter makes Node 18 enter every tracing branch without subscribers.
## Fix Focus Areas
- packages/pg/lib/diagnostics.js[23-29]
- packages/pg-pool/diagnostics.js[27-33]
## Recommended Fix
When the aggregate getter is unavailable, check the constituent tracing channels' subscriber flags instead of assuming subscribers exist; retain the noop-channel behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread packages/pg/lib/client.js
Comment on lines +709 to +710
if (shouldTrace(queryChannel) && query.callback) {
const context = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

6. Rejected requests leave no trace 🐞 Bug ◔ Observability

Both new tracing setups sit after existing early-return guards: Client.query() skips tracing for
an unqueryable or closing client, and Pool.connect() skips it after pool shutdown. When those
guards reject callback or promise requests, callers receive errors but subscribers receive no
corresponding start or error events.
Agent Prompt
## Issue description
Early rejection paths return before the new tracing wrappers are installed.
## Fix Focus Areas
- packages/pg/lib/client.js[685-735]
- packages/pg-pool/index.js[195-227]
## Recommended Fix
Include the early failure paths within each operation's tracing setup so their existing callback or promise rejection also publishes a trace error.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread packages/pg/lib/client.js
Comment on lines +726 to +730
queryChannel.traceCallback(
(tracedCb) => {
query.callback = tracedCb
enqueue()
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

7. Cancelled queries leave open traces 🐞 Bug ◔ Observability

The added query wrapper starts a trace when it enqueues a query, but queued-query cancellation
removes that query without calling its traced callback. If a caller cancels a query while another is
active, subscribers retain a start event with no completion or error event.
Agent Prompt
## Issue description
Cancelling a queued query removes it without completing the trace begun at enqueue time.
## Fix Focus Areas
- packages/pg/lib/client.js[542-560]
- packages/pg/lib/client.js[703-739]
## Recommended Fix
Give queued queries a trace-completion path on cancellation, or defer starting their trace until execution begins, without changing the existing caller cancellation behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 6 potential issues.

Devin Review

Comment thread packages/pg-pool/index.js
@@ -1,5 +1,6 @@
'use strict'
const EventEmitter = require('events').EventEmitter
const { poolConnectChannel, poolReleaseChannel, poolRemoveChannel, shouldTrace } = require('./diagnostics')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Published pool package cannot load

Packed installs of pg-pool omit the required diagnostics module. The package allowlist includes only index.js and esm, so importing the published pool fails.

Learn more

The pool package uses a files allowlist to choose which files enter the published tarball. It currently includes index.js and esm, but not the new module required here. A checkout works because the module exists locally; an installed tarball lacks it and cannot load the pool.

Example: A service installs the newly published pg-pool and runs require('pg-pool'). Module resolution reaches require('./diagnostics'), cannot find the file, and throws before the service creates a pool.

Recommended fix: Add diagnostics.js to the files array in the package manifest, then inspect the packed tarball and load the installed package in a packaging test.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread packages/pg/lib/client.js
Comment on lines +726 to +734
queryChannel.traceCallback(
(tracedCb) => {
query.callback = tracedCb
enqueue()
},
0,
context,
null,
enrichedCb

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Timed-out queries leave tracing spans open

When query_timeout fires, query.callback is replaced before the tracing wrapper receives the error. The timeout handler calls an earlier callback directly, so subscribers never receive an error or asyncEnd.

Learn more

Query timeouts save a callback before tracing wraps query.callback. When the timeout fires, the timer calls the saved callback directly and replaces query.callback with a no-op. TracingChannel observes completion only through its wrapped callback, so the timeout leaves the trace without an error or asyncEnd event, even though the caller gets an error.

Example: A client uses { text: 'SELECT pg_sleep(10)', query_timeout: 10 } with a query-channel subscriber. The subscriber sees start/end, the caller receives Query read timeout, but no error or asyncEnd arrives for that query.

Recommended fix: Route the timeout's completion through the traced callback exactly once. Coordinate the timer's saved callback and the later query.callback replacement in the timeout handler so a later server reply does not complete the trace twice.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread packages/pg/lib/client.js
this._pulseQueryQueue()
}

if (shouldTrace(queryChannel) && query.callback) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Event-based queries disappear from query tracing

When a submittable query has no callback, query.callback is empty and tracing is skipped. Query events still complete the query, leaving its execution invisible to subscribers.

Learn more

client.query(new Query('SELECT 1')) returns a query object that reports rows and completion through events rather than a callback. The tracing gate requires a callback, so this supported query form bypasses the query tracing channel entirely. Its end and error events still fire through Query completion.

Example: An application attaches an end listener to client.query(new Query('SELECT 1')) while an instrumenter subscribes to pg:query. The listener runs, but the instrumenter receives no start or completion event.

Recommended fix: Trace the event-based query lifecycle separately, without assigning a callback to queries that did not have one; Query.handleRowDescription uses callback presence to decide whether to buffer rows.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread packages/pg/lib/client.js
}
const origCb = query.callback
const enrichedCb = (err, res) => {
if (res) context.result = { rowCount: res.rowCount, command: res.command }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Multi-statement traces lose result metadata

For multi-statement queries, res is an array, so context.result gets undefined rowCount and command. Query results contain those fields on each array element instead.

Learn more

A simple query can execute more than one statement. Query stores each statement's result separately and passes an array to the callback. This trace mapping reads metadata from the array itself, where neither rowCount nor command exists, so a successful multi-statement trace contains no useful result metadata.

Example: For SELECT 1; SELECT 2, the callback receives two Result objects, but the diagnostic context contains { rowCount: undefined, command: undefined }.

Recommended fix: Handle an array of results explicitly in the enrichment callback, preserving the per-statement command and row count while retaining the current shape for single statements.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +23 to +28
// Check explicitly for `false` rather than truthiness because the aggregated
// `hasSubscribers` getter on TracingChannel is `undefined` on Node 18 (which
// backported TracingChannel but not the getter). When `undefined`, we assume
// there may be subscribers and trace unconditionally.
function shouldTrace(channel) {
return channel.hasSubscribers !== false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Unsubscribed Node 18 tracing has overhead

On Node 18, an undefined hasSubscribers makes shouldTrace enter tracing paths without subscribers. Benchmark this compatibility path before claiming subscriber-free overhead is negligible.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +9 to +15
const hasStableTracingChannel =
typeof dc.tracingChannel === 'function' && typeof dc.tracingChannel('pg:test:probe').hasSubscribers === 'boolean'

const suite = new helper.Suite()
const test = suite.test.bind(suite)
// pass undefined as callback to skip when TracingChannel is unavailable/unstable
const testTracing = (name, cb) => test(name, hasStableTracingChannel ? cb : undefined)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Node 18 compatibility path lacks tests

The tracing tests skip when hasSubscribers is undefined, while production deliberately traces in that case. Consider isolated Node 18 coverage that avoids unstable unsubscribe behavior.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@MiichelleZhou
MiichelleZhou deleted the eval/3650/qodo/round-6 branch September 28, 2026 18:58

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b922f0d. Configure here.

Comment thread packages/pg-pool/index.js
@@ -1,5 +1,6 @@
'use strict'
const EventEmitter = require('events').EventEmitter
const { poolConnectChannel, poolReleaseChannel, poolRemoveChannel, shouldTrace } = require('./diagnostics')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pool diagnostics file not published

High Severity

index.js now requires ./diagnostics, but diagnostics.js is not in the pg-pool files allowlist. After publish, loading the pool throws a missing-module error for every pg and pg-pool consumer.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b922f0d. Configure here.

Comment thread packages/pg/lib/client.js
context,
null,
enrichedCb
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timeout skips query tracing callback

Medium Severity

A query_timeout invokes the original callback and then replaces query.callback with a no-op, so the pg:query wrapper never runs. Subscribers receive start without error or asyncEnd, leaving traces open.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b922f0d. Configure here.

Comment thread packages/pg/lib/client.js
context,
null,
enrichedCb
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed queries skip diagnostic events

Medium Severity

pg:query wrapping happens only after the !_queryable and _ending checks return. Those failed query() calls never publish channel events, so subscribers miss a common class of errors. connect() wraps first and does not have this gap.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b922f0d. Configure here.

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.

2 participants