Skip to content

fix(debug): log structured HTTP error details instead of raw response#1233

Merged
John-David Dalton (jdalton) merged 7 commits intomainfrom
fix/http-error-structured-logging
Apr 21, 2026
Merged

fix(debug): log structured HTTP error details instead of raw response#1233
John-David Dalton (jdalton) merged 7 commits intomainfrom
fix/http-error-structured-logging

Conversation

@jdalton
Copy link
Copy Markdown
Contributor

@jdalton John-David Dalton (jdalton) commented Apr 18, 2026

Summary

When an HTTP request fails, debugApiResponse previously logged a structured payload missing three pieces of information that make support tickets actionable: a request timestamp, the Cloudflare trace id, and the response body.

This PR adds those fields without changing the shape for callers that don't provide them.

New fields on ApiRequestDebugInfo

Field What it gives you
requestedAt ISO-8601 timestamp of request start. Correlates with server-side logs.
responseHeaders Sanitized copy of the server's response headers.
responseBody The response body string, truncated at 2 000 bytes so mega-payloads don't bloat debug logs.
cfRay (derived) Extracted from responseHeaders['cf-ray'] or ['CF-Ray'] and promoted to a top-level field — one-glance look-up for filing against Cloudflare.

Callers updated

  • queryApiSafeText — success branch logs requestedAt; failure branch adds responseHeaders + responseBody.
  • sendApiRequest — same pattern.

Both callers also passed responseHeaders only on the !result.ok path (not on the network-exception path, since there's no response in that case).

Existing behavior preserved

Request header sanitization (Authorization / *api-key* redacted) unchanged. Callers that don't pass the new fields see no difference in the logged payload.

Tests

Added 5 new assertions in test/unit/utils/debug.test.mts:

  • requestedAt flows through
  • cfRay extracted from cf-ray and CF-Ray casings
  • responseBody echoed through
  • bodies over 2 000 bytes get truncated with a "… (truncated, N bytes)" suffix

Test plan

  • pnpm run type clean
  • pnpm --filter @socketsecurity/cli run test:unit — 339 files / 5109 tests pass (was 5106; +3 unique assertions after dedup)
  • pnpm run build:cli succeeds
  • CI green

Note

Medium Risk
Changes CLI debug output for failed HTTP calls to include response headers and truncated bodies, which could inadvertently expose sensitive data in logs if enabled. Logic is localized to debugging/error paths but touches widely-used API request helpers.

Overview
Improves API failure diagnostics by extending debugApiResponse to emit a consistent structured payload that can include requestedAt, sanitized responseHeaders, extracted cfRay, and a truncated responseBody.

Updates queryApiSafeText and sendApiRequest to capture requestedAt on start and, on non-OK responses, pass response headers/body into debug logging via a new safe tryReadResponseText helper. Adds unit tests covering timestamp propagation, cf-ray extraction (case-insensitive), and response body truncation.

Reviewed by Cursor Bugbot for commit 9dffe43. Configure here.

Comment thread packages/cli/src/utils/socket/api.mts Outdated
Comment thread packages/cli/src/utils/debug.mts Outdated
John-David Dalton (jdalton) added a commit that referenced this pull request Apr 18, 2026
Addresses Cursor bugbot feedback on PR #1233.

1. `result.text?.()` in the `!result.ok` branches of `queryApiSafeText`
   and `sendApiRequest` was unguarded. If `text()` threw, the exception
   propagated past the clean `{ ok: false, ... }` return and broke the
   error-handling contract. Wrap both call sites in a shared
   `tryReadResponseText` helper that swallows the failure and returns
   `undefined`.

2. The truncation suffix reported `body.length` as "bytes", but
   `String.prototype.length` / `String.prototype.slice` count UTF-16
   code units, not bytes. For non-ASCII response bodies the label was
   misleading. Rename to "chars" so the counter matches the actual
   measurement.
@jdalton
Copy link
Copy Markdown
Contributor Author

Cursor (@cursor) review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

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

Reviewed by Cursor Bugbot for commit 9dffe43. Configure here.

@jdalton
Copy link
Copy Markdown
Contributor Author

Cursor (@cursor) review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

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

Reviewed by Cursor Bugbot for commit 9dffe43. Configure here.

@jdalton
Copy link
Copy Markdown
Contributor Author

Cursor (@cursor) review

1 similar comment
@jdalton
Copy link
Copy Markdown
Contributor Author

Cursor (@cursor) review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

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

Reviewed by Cursor Bugbot for commit 9dffe43. Configure here.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

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

Reviewed by Cursor Bugbot for commit 9dffe43. Configure here.

@jdalton
Copy link
Copy Markdown
Contributor Author

Cursor (@cursor) review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

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

Reviewed by Cursor Bugbot for commit 9dffe43. Configure here.

@jdalton
Copy link
Copy Markdown
Contributor Author

Cursor (@cursor) review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

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

Reviewed by Cursor Bugbot for commit 9dffe43. Configure here.

Comment thread packages/cli/src/utils/debug.mts
When an HTTP request fails, `debugApiResponse` now logs:

  * endpoint (description) — already was
  * status                 — already was
  * method, url, durationMs — already was
  * sanitized request headers (Authorization/api-keys redacted) — already was

And new fields that make support tickets actionable:

  * requestedAt — ISO-8601 timestamp of request start, for correlating
    with server-side logs.
  * cfRay       — Cloudflare trace id extracted as a top-level field
    from response headers (accepts `cf-ray` or `CF-Ray` casing).
  * responseHeaders — sanitized headers returned by the server.
  * responseBody   — string response body, truncated at 2_000 bytes
    so megabyte payloads don't balloon debug logs.

Wired into both the `queryApiSafeText` and `sendApiRequest` !ok
branches, which are the primary points where a non-thrown HTTP
error reaches a user. The success-path log includes the new
`requestedAt` timestamp too.

Added 5 new tests in `test/unit/utils/debug.test.mts` covering
requestedAt, cfRay (both casings), body passthrough, and body
truncation.

Existing debug-output shape preserved: callers that don't pass the
new fields (`responseHeaders`, `responseBody`, `requestedAt`) see
no change.
Addresses Cursor bugbot feedback on PR #1233.

1. `result.text?.()` in the `!result.ok` branches of `queryApiSafeText`
   and `sendApiRequest` was unguarded. If `text()` threw, the exception
   propagated past the clean `{ ok: false, ... }` return and broke the
   error-handling contract. Wrap both call sites in a shared
   `tryReadResponseText` helper that swallows the failure and returns
   `undefined`.

2. The truncation suffix reported `body.length` as "bytes", but
   `String.prototype.length` / `String.prototype.slice` count UTF-16
   code units, not bytes. For non-ASCII response bodies the label was
   misleading. Rename to "chars" so the counter matches the actual
   measurement.
@jdalton John-David Dalton (jdalton) force-pushed the fix/http-error-structured-logging branch from a0e7c42 to d5e9a52 Compare April 21, 2026 15:10
CI was failing with `pnpm: 1: This: not found` (exit 127) during the
install step because @pnpm/exe@11.0.0-rc.2 ships a broken shell shim —
its pnpm binary begins with "This..." which the shell tries to execute
as a command. Affects main too, not just this PR.

- Bump `packageManager` from `pnpm@11.0.0-rc.2` to `pnpm@11.0.0-rc.3`.
- Bump `engines.pnpm` to `>=11.0.0-rc.3` to match.
- Regenerate `pnpm-lock.yaml` with rc.3 — the new lockfile no longer
  pulls @pnpm/exe into packageManagerDependencies, which is why the
  footprint shrinks. @pnpm/exe stays in the allowBuilds allowlist in
  `pnpm-workspace.yaml` in case it's ever resolved transitively.

The socket-registry setup-and-install action (SHA a5923566c) already
installs pnpm rc.3, matching the upstream `_local-not-for-reuse-*`
workflows — so action pins do not need to change.
@jdalton John-David Dalton (jdalton) merged commit 35b9b9c into main Apr 21, 2026
7 of 8 checks passed
@jdalton John-David Dalton (jdalton) deleted the fix/http-error-structured-logging branch April 21, 2026 15:21
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