Skip to content

Request summaries, coded errors and edge SDK (Runtime 1.5.0) - #25

Merged
sagnik11 merged 7 commits into
mainfrom
feat/wide-events-coded-errors
Oct 8, 2026
Merged

sagnik11 merged 7 commits into
mainfrom
feat/wide-events-coded-errors

Conversation

@sagnik11

@sagnik11 sagnik11 commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

A failed request today leaves scattered evidence: a sampled trace, maybe an exception, and log lines nobody can tie together. Runtime 1.5.0 gives every request and job one summary record with a request id, and lets applications attach a stable error code plus the cause and fix they already know. The same code from any source becomes one issue.

SDK (runtime-node / runtime-next 1.5.0, new runtime-edge 1.0.0).

  • autterRequests, autterFastify and withRuntimeRequest emit a summary per request: route, status, duration, outcome, request id (x-request-id, honoured or generated, CORS-exposed), plus debug/info messages folded into the summary. Summaries are always kept; only ignore globs skip routes.
  • runtimeContext adds context, outcomes and logs from anywhere in a request.
  • RuntimeError, defineRuntimeErrors and autterErrorResponse carry code, why, fix, link and expected. captureException reads those fields from any error.
  • Carriers link queued jobs back to the request that enqueued them, and AI usage rolls up onto the summary.
  • initAutterLogging brings all of this to apps that already run their own OpenTelemetry SDK. Enrichers, a pretty dev console, .autter/runtime/*.jsonl files and a /testing subpath round it out.
  • @autter/runtime-edge covers Workers, Vercel Edge, Deno and Bun with no dependencies.
  • Shared code lives in a private runtime-core package that is bundled in, not published.

Ingester 1.5.0.

  • Coded errors get a source-independent code-v1 fingerprint. Uncoded errors keep today's fingerprint byte-for-byte.
  • Migrations 0012–0014 add request and error-code columns plus a per-route runtime_request_1m rollup.
  • /v1/logs promotes error records from logger-only and edge mode to issues, deduplicated by trace id.
  • The sink carries the new fields as optional properties and stays version: 1.
  • LOG_TTL_DAYS makes log retention configurable.

Browser 1.4.0. Coded fields and x-request-id are attached to captured failures, and autterErrorFromResponse builds an error from an API error body. It also fixes a require entry that pointed at a file the build never produced. The bundle is 3.87 kB of the 5 kB budget.

Validation. npm run build passed, and all package tests passed: core 13, node 56, edge 4, next 3, ingester 79, browser 11. A golden test shows 1.4.0 operation output is byte-identical apart from the two new summary attributes (autter.operation.kind, autter.operation.level), which it strips before comparing. The new ClickHouse DDL is covered by string-level tests only; it hasn't been run against a live ClickHouse, because Docker wasn't available.

Rollout. Deploy ingester 1.5.0 first, then publish the SDKs. The platform changes are in Autter-dev/autter-monorepo (branch claude/evlog-runtime-improvement-c55ed6), and the agent skills are in Autter-dev/autter-skills (branch feat/requests-coded-errors); merge those after these packages are published. Design: docs/REQUESTS-AND-ERRORS.md.

🤖 Generated with Claude Code


View code changes stack in Autter

Summary

Summary generated by Autter.

Why this change

Runtime telemetry lacked per-request summaries, stable developer-declared error codes (with cause/fix/link metadata) for grouping, and any support for edge runtimes like Cloudflare Workers. This release (Runtime 1.5.0) adds those capabilities across the SDKs and the ingester.

What changes for users

Node, Next.js, browser and a new edge SDK can emit always-kept request summary logs and coded errors carrying autter.error.* and autter.request.id. The ingester lifts these fields, groups coded errors under a new source-independent code-v1 fingerprint, stores request summaries and the new columns via migrations, and forwards the new fields to the sink webhook. Shared logic (redaction, errors, request context, carriers) moves into a new @autter/runtime-core package, with new docs, an edge-worker example, and golden-fixture tests.

Implementation and review notes

  • New packages runtime-core (shared redact/errors/record/context/fingerprint-code) and runtime-edge; runtime-node/src/redact.ts shrinks to a re-export, and runtime-node gains requests.ts, sinks.ts, logging-only.ts, enrichers.ts and testing.ts.
  • Fingerprinting changes in fingerprint.ts: a valid error code switches to code-v1 grouping (message-v1 unchanged for uncoded errors). Per repo rules this is a breaking regroup and needs prior discussion; the new doc comment's "existing issues never regroup" claim should be checked, as should whether id-shaped lowercase codes can mint unbounded issues.
  • normalize-browser.ts calls liftErrorFields(event.context) on the raw context rather than the scrubContext output, so why/fix/link can bypass email/sensitive-key masking. Review against the whitelist-privacy rule.
  • sink.ts sinkOccurrence now omits traceId/route/method/statusCode when empty and uses a truthiness check that drops statusCode 0. This risks changing existing v1 webhook field semantics, which should stay additive-only.
  • ClickHouse changes in migrations.ts and clickhouse.ts (OCCURRENCE_CODE_COLUMNS, request-id index, runtime_logs and request rollup tables, LOG_TTL_DAYS). Confirm migrations are idempotent and the columns have defaults.
  • Fail-open concerns in the browser SDK: errorContext reads duck-typed fields (code, why, requestId) without guarding property access, so a throwing getter could abort captureException. Also verify the runtime-next dts build resolves @autter/runtime-edge; the earlier npm test walk failed on this, and scripts/ensure-built.mjs is added.

Changes

  • @autter/runtime-core (new): shared redact.ts (moved from runtime-node), errors.ts (coded errors), record.ts, context.ts, request.ts, carrier.ts, otlp-logs.ts, user-agent.ts, attributes.ts and fingerprint-code.ts, plus fixtures/code-fingerprints.json and test/core.test.mjs.
  • @autter/runtime-edge (new): edge SDK in src/index.ts with README, tsup/tsconfig and test/edge.test.mjs; examples/edge-worker shows a Cloudflare Worker integration; runtime-next/src/edge.ts re-exports it.
  • @autter/runtime-node:
    • logger.ts is substantially reworked and gains new operation APIs.
    • New modules: requests.ts (request summaries), sinks.ts, logging-only.ts, enrichers.ts, testing.ts and errors.ts.
    • server.ts and llm-instrument.ts are updated, and new exports are added in index.ts.
    • redact.ts is now a thin re-export of runtime-core.
    • Tests are added for requests, operations, sinks, logging-only and hook mode, with a golden-1.4.0-logs.json fixture and a golden test.
  • @autter/runtime-next: src/server.ts adds request-summary and coded-error exports and a new route handler; client.ts has a one-line export addition; package/tsup config updated for the edge entry; test/next.test.mjs added.
  • @autter/runtime-browser: redactContext is extended and errorContext adds coded-error and request-id metadata to captureException and captureMessage; test/coded-errors.test.mjs added.
  • @autter/otlp-ingester:
    • error-fields.ts (new) validates and lifts autter.error.* and autter.request.id.
    • fingerprint.ts adds the code-v1 scheme alongside unchanged message-v1.
    • types.ts adds optional errorCode, why, fix, link, expected, requestId, fingerprintScheme.
    • normalize-browser.ts and normalize-otlp.ts lift the new fields.
    • logs.ts and the new log-promotion.ts handle request summaries and promotion, with new PROMOTION_LOOKUPS_PER_MINUTE and LOG_TTL_DAYS config.
    • migrations.ts and clickhouse.ts add code columns, a request-id index and the request rollup table.
    • server.ts wires promotion and per-tenant lookup rate limiting.
    • sink.ts forwards the new fields via sinkOccurrence.
    • context.ts adjusts sanitisation, and coded-errors.test.ts (684 lines) is added.
  • Docs and tooling:
    • New docs/REQUESTS-AND-ERRORS.md; updates to ARCHITECTURE, OPERATION-LOGGING, CONTINUOUS-DETECTION, GETTING-STARTED, INTEGRATIONS, EXTERNAL-SOURCES, PLAN, READMEs and CHANGELOG.
    • scripts/ensure-built.mjs is added; ci.yml, release.yml, root package.json, package-lock.json and .gitignore are updated.
    • examples/express-app is updated to demonstrate the new APIs.

Breaking changes

  • Fingerprint regroup: an error that starts declaring a valid autter.error.code moves from message-v1 to code-v1. New occurrences open a new issue and the prior message-grouped issue stops receiving them. Uncoded errors are unaffected. Repo rules require discussing fingerprint changes in an issue first.
  • Sink webhook v1 shape: sinkOccurrence now omits traceId, route, method and statusCode when empty (previously always present as null), and drops statusCode: 0. This could affect consumers that rely on key presence, and it conflicts with the additive-only wire rule unless restored.
  • Additive surfaces: the /v1/browser v1 payload, OTLP surface, ClickHouse columns (with defaults) and runtime-node exports only gain optional fields. runtime-node/src/redact.ts now re-exports from @autter/runtime-core, which becomes a new runtime dependency of the packages that use it.

Acceptance Criteria

  • A valid autter.error.code yields fingerprintScheme: "code-v1" and the same fingerprint regardless of source, message or route. Uncoded errors still produce byte-identical message-v1 fingerprints.
  • Id-shaped or malformed codes (for example req_8f3a1c4d, uppercase ECONNRESET) do not mint separate code-v1 issues.
  • why, fix and link from /v1/browser are validated and masked (email and sensitive-key redaction) consistently with the stored context, and undeclared keys never reach storage.
  • Sink webhook v1 payloads keep traceId, route, method and statusCode present (null when unknown), preserve statusCode: 0, and add only the new optional 1.5.0 fields.
  • Request summaries are always recorded (never sampled) and joinable to occurrences by autter.request.id; runtime_logs honours LOG_TTL_DAYS.
  • Migrations are versioned, idempotent and append-only, with DEFAULTs on new columns, and match schemaStatements() in clickhouse.ts.
  • captureException, captureMessage and the global handlers in runtime-browser never throw, even when error metadata getters throw. The browser bundle stays under 5 KB brotlied with zero dependencies.
  • npm run build and npm test pass from a clean checkout, including the runtime-next dts build that resolves @autter/runtime-edge and @autter/runtime-browser. The runtime-node golden test matches golden-1.4.0-logs.json.
  • The edge SDK runs in a Workers-style runtime (the examples/edge-worker example) and emits request summaries and coded errors without Node-only APIs.

Test Plan

  • Run npm ci && npm run build, then npm test, and confirm the runtime-next dts build resolves @autter/runtime-edge (exercises scripts/ensure-built.mjs).
  • Run npm run size -w @autter/runtime-browser and confirm the bundle is within the 5 KB brotlied budget.
  • Start local ClickHouse and the ingester (Docker Compose, ports 8123 and 4318). Confirm migrations apply twice without error and that runtime_occurrences has the new code columns and request-id index, and runtime_logs has its TTL.
  • POST a /v1/browser beacon with autter.error.code: "payments.card_declined", autter.error.why/fix/link and autter.request.id. Read back from ClickHouse and confirm fingerprintScheme = "code-v1" and the lifted fields.
  • Repeat the beacon with a different message and route but the same code, and confirm the same fingerprint.
  • Send two beacons with id-shaped codes (req_8f3a1c4d, req_9b2e77aa) and identical stacks. Confirm they do not create separate code-v1 issues.
  • Send a beacon with autter.error.why: "declined for jane@acme.com" and confirm the stored and sink-forwarded why has the email masked, consistent with attributes.context.
  • Send an uncoded error and confirm its fingerprint equals the one produced on main (message-v1, unchanged).
  • Configure AUTTER_SINK_URL to a local receiver and inspect the webhook body. Confirm traceId, route, method and statusCode are present (including null and 0) and the new fields appear only when set.
  • Run examples/express-app and hit /boom, /api/ok and POST /api/checkout. Confirm request summaries are emitted and linked to occurrences via autter.request.id.
  • Run examples/edge-worker with wrangler dev and confirm a request summary and a coded error reach the ingester.
  • In a browser test page, throw an Error subclass whose code getter throws and confirm captureException still enqueues the original exception.
  • Replay golden-1.4.0-logs.json through runtime-node and confirm the golden test passes with no schema drift.

Rollback Plan

  • Revert the PR merge commit on main and redeploy. For AWS, register the previous ECS task definition revision and roll the service. For single-server, pull the prior ingester image tag and recreate only the ingester service.
  • Migrations are append-only and additive with column defaults, so older ingester code runs against the new schema. Do not drop the new columns, request-id index or runtime_logs/request rollup tables on rollback.
  • If only the fingerprint behaviour is a problem, stop producers from sending autter.error.code (or pin SDKs to 1.4.x). Ingestion then falls back to message-v1 grouping. Occurrences already stored as code-v1 keep their fingerprints; the issue consumer should regroup or ignore them.
  • If the sink payload change breaks a webhook consumer, redeploy the previous ingester image. Batches lost from the in-memory buffer can be replayed from ClickHouse using the logged time range (see ARCHITECTURE.md "Sink webhook").
  • Revert the SDK packages by unpublishing or deprecating 1.5.0 on npm and pinning applications to the previous 1.4.x versions.
  • After rolling back, confirm /healthz reports ok, the sink queue drains, and fresh occurrences appear in ClickHouse.

Related Issues

No linked issue was identified in the branch name, commit context or diff. Because this PR changes fingerprinting, which repo rules say must be discussed in an issue first, please link that discussion here if one exists.

Written for commit be20986. Summary will update on new commits.

sagnik11 and others added 2 commits October 8, 2026 00:01
…omotion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ime-edge

Adds the private runtime-core package (coded errors, record building, OTLP
logs encoding, redaction) bundled into runtime-node 1.5.0 and the new
@autter/runtime-edge 1.0.0. runtime-node gains request summaries
(autterRequests, autterFastify, withRuntimeRequest), runtimeContext,
inline messages, RuntimeError/defineRuntimeErrors/autterErrorResponse,
fork/carrier, AI rollup, waitUntil, enrichers, sinks, a /testing subpath
and initAutterLogging. 1.4.0 output is golden-tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@autter-dev

autter-dev Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

🚦 Pre-merge checks · ⚠️ 18 warning, ✅ 185 passed

Needs attention

Check Status Explanation
Too many files changed ⚠️ Warning This PR changes 96 files (limit 25 lowered from 50). Consider splitting this PR by area: packages (74 files), docs (8 files), examples (6 files), (root) (5 files), .github (2 files), scripts/ensure-built.mjs (1 file). This PR touches high-impact code whose failure cascades downstream: `packages/otlp-ingester/src/types. ... [truncated 58 chars]
Too many lines changed ⚠️ Warning This PR changes 10922 lines (limit 500 lowered from 1000). Splitting into smaller, focused PRs makes review more reliable. This PR touches high-impact code whose failure cascades downstream: packages/otlp-ingester/src/types.ts (blast 70, fan-in 11), so the bar for review is lower.
Mixed concerns (refactor + behavior change) ⚠️ Warning The PR moves redaction into the new runtime-core package, leaving runtime-node/src/redact.ts as a re-export. That is a pure refactor. The same PR adds request summaries, coded errors, a new code-v1 fingerprint that regroups issues, and sink payload changes that omit fields when empty. It also reworks logger.ts (632 add ... [truncated 516 chars]
Removed observability ⚠️ Warning 3 potential issue(s) detected (max risk 61/100): packages/runtime-node/src/logger.ts:251, packages/runtime-node/src/logger.ts:266, packages/runtime-node/src/logger.ts:376.
Possible TOCTOU in critical path ⚠️ Warning 1 potential issue(s) detected (max risk 20/100): packages/otlp-ingester/src/server.ts:348.
PII stored without application-level encryption ⚠️ Warning 3 potential issue(s) detected (max risk 93/100): packages/runtime-edge/src/index.ts:72, packages/runtime-node/src/logging-only.ts:51, packages/runtime-node/src/sinks.ts:38.
Lockfile resolution / integrity tampered ⚠️ Warning 1 potential issue(s) detected (max risk 75/100): package-lock.json:3438.
Possible dependency-confusion attack ⚠️ Warning 1 potential issue(s) detected (max risk 39/100): examples/edge-worker/package.json:12.
Inconsistent error handling ⚠️ Warning 1 potential issue(s) detected (max risk 20/100): packages/runtime-core/src/fingerprint-code.ts:19.
Hallucinated import (package not installed) ⚠️ Warning 1 potential issue(s) detected (max risk 100/100): packages/otlp-ingester/src/log-promotion.ts:3.
Generic placeholder identifier in production logic ⚠️ Warning 12 potential issue(s) detected (max risk 51/100): packages/otlp-ingester/src/error-fields.ts:44, packages/runtime-core/src/carrier.ts:26, packages/runtime-core/src/context.ts:117, packages/runtime-core/src/fingerprint-code.ts:20, packages/runtime-core/src/redact.ts:205.
Comment contradicts or fabricates code behaviour ⚠️ Warning 1 potential issue(s) detected (max risk 58/100): packages/otlp-ingester/src/fingerprint.ts:312.
Access token in source ⚠️ Warning 1 potential secret(s) detected (max risk 100/100): packages/runtime-core/test/core.test.mjs:257.
Database connection URL with embedded credentials ⚠️ Warning 1 potential issue(s) detected (max risk 90/100): packages/runtime-core/test/core.test.mjs:256.
Co-change coupling ⚠️ Warning 1 file(s) changed without a partner they usually change with.
Intent vs. implementation ⚠️ Warning The visible ingester-side changes (code-based fingerprinting, error-field lifting, request-id linking, log TTL) match the stated intent, but most of the SDK deliverables sit in omitted files and could not be verified. Requirements: 4 implemented, 2 partial. 2 change(s) with no backing requirement. (Intent from product ... [truncated 25 chars]
Complexity Guard ⚠️ Warning 1 finding(s) on changed lines.
Release Notes Curator ⚠️ Warning 20 root-cause issue(s) detected (max risk 100/100): packages/otlp-ingester/src/fingerprint.ts:280, packages/otlp-ingester/src/normalize-browser.ts:211, packages/runtime-core/test/core.test.mjs:32, packages/runtime-node/test/requests.test.mjs:48, packages/otlp-ingester/src/sink.ts:437.
✅ Passed checks (185)
Check Status Explanation
Too many unrelated chapters ✅ Passed 8 chapter(s) detected, within the limit of 6.
Generated files hiding real changes ✅ Passed Generated-file volume (68 lines) does not obscure the 10854 hand-written line(s).
Missing PR context ✅ Passed PR context looks sufficient.
Migration + app logic + UI combined in one PR ✅ Passed The PR includes ClickHouse migrations (migrations.ts, clickhouse.ts) and application logic, but it has no user-interface changes. The browser SDK and the edge example are libraries and sample code, not UI components or screens.
Sensitive data in logs ✅ Passed No sensitive data in logs issues detected.
Log injection ✅ Passed No log injection issues detected.
Missing audit logging ✅ Passed No missing audit logging issues detected.
Silent exception swallowing ✅ Passed No silent exception swallowing issues remaining after verification.
Unhandled promise rejection ✅ Passed No unhandled promise rejection issues remaining after verification.
Circuit breaker not detected ✅ Passed No circuit breaker not detected issues remaining after verification.
Stack trace leakage ✅ Passed No stack trace leakage issues detected.
Multi-write without detected transaction ✅ Passed No multi-write without detected transaction issues remaining after verification.
Idempotency key not detected ✅ Passed No idempotency key not detected issues remaining after verification.
Possible non-atomic read-modify-write ✅ Passed No possible non-atomic read-modify-write issues remaining after verification.
Optimistic locking not detected ✅ Passed No optimistic locking not detected issues remaining after verification.
Rate limiting not detected ✅ Passed No rate limiting not detected issues detected.
Rate limiting removed ✅ Passed No rate limiting removed issues detected.
Batch size limit not detected ✅ Passed No batch size limit not detected issues remaining after verification.
Pagination not detected ✅ Passed No pagination not detected issues remaining after verification.
Prompt injection risk ✅ Passed No LLM/AI-integration code touched by this diff.
LLM output used in a dangerous sink ✅ Passed No LLM/AI-integration code touched by this diff.
Sensitive data in prompt or system-prompt leakage ✅ Passed No LLM/AI-integration code touched by this diff.
Over-privileged LLM tool / excessive agency ✅ Passed No LLM/AI-integration code touched by this diff.
Missing validation on an LLM-driven decision ✅ Passed No LLM/AI-integration code touched by this diff.
Unbounded LLM usage (denial-of-wallet) ✅ Passed No LLM/AI-integration code touched by this diff.
Table exposed without row-level security ✅ Passed No row-level-security-related code touched by this diff.
Over-broad row-level security policy ✅ Passed No row-level-security-related code touched by this diff.
Code path that bypasses row-level security ✅ Passed No row-level-security-related code touched by this diff.
Privileged database credential reachable from the client ✅ Passed No row-level-security-related code touched by this diff.
Privileged query without row-level scoping ✅ Passed No row-level-security-related code touched by this diff.
Row-level security enabled with no policy ✅ Passed No row-level-security-related code touched by this diff.
Template-default gradient styling ✅ Passed No added frontend pages or design-slop markers in this diff.
Interchangeable AI marketing copy ✅ Passed No added frontend pages or design-slop markers in this diff.
Placeholder content shipped to users ✅ Passed No added frontend pages or design-slop markers in this diff.
Emoji standing in for an icon system ✅ Passed No added frontend pages or design-slop markers in this diff.
Call-to-action that goes nowhere ✅ Passed No added frontend pages or design-slop markers in this diff.
Templated page composition ✅ Passed No added frontend pages or design-slop markers in this diff.
Color outside the brand palette ✅ Passed No added frontend markup or brand-relevant style changes in this diff.
Hardcoded style bypassing design tokens ✅ Passed No added frontend markup or brand-relevant style changes in this diff.
Typography outside the brand type system ✅ Passed No added frontend markup or brand-relevant style changes in this diff.
One-off UI instead of the shared component ✅ Passed No added frontend markup or brand-relevant style changes in this diff.
Copy that does not match brand voice ✅ Passed No added frontend markup or brand-relevant style changes in this diff.
Screen does not match the rest of the product ✅ Passed No added frontend markup or brand-relevant style changes in this diff.
Merge-blocking marker left in the change ✅ Passed No pending-work markers added by this diff.
Known-defect marker shipped in code ✅ Passed No pending-work markers added by this diff.
Untracked TODO without an issue reference ✅ Passed No pending-work markers added by this diff.
Test disabled or left pending ✅ Passed No pending-work markers added by this diff.
PII in logs ✅ Passed No pii in logs issues detected.
PII or internals leaked in error response ✅ Passed No pii or internals leaked in error response issues detected.
User data stored without retention controls ✅ Passed No user data stored without retention controls issues detected.
PII sent to external / cross-border destination ✅ Passed No pii sent to external / cross-border destination issues detected.
Dependency runs install-time lifecycle script ✅ Passed No dependency runs install-time lifecycle script issues detected.
Lockfile resolves a dependency the manifest does not declare ✅ Passed No lockfile resolves a dependency the manifest does not declare issues remaining after verification.
Checked-in build artefact modified without source change ✅ Passed No checked-in build artefact modified without source change issues detected.
Dockerfile build-step is insecure ✅ Passed No dockerfile build-step is insecure issues detected.
External artefact pulled in without integrity pinning ✅ Passed No external artefact pulled in without integrity pinning issues detected.
Changed export, importer not updated ✅ Passed No changed export with an un-updated importer detected.
Source changes without matching tests ✅ Passed No source changes without matching tests issues remaining after verification.
Migration missing rollback / down step ✅ Passed No migration missing rollback / down step issues remaining after verification.
Frontend importing database client directly ✅ Passed No frontend importing database client directly issues detected.
Backend service importing UI module ✅ Passed No backend service importing ui module issues detected.
Cross-context internals import ✅ Passed No cross-context internals import issues detected.
Workspace package rule violation ✅ Passed No workspace package rule violation issues detected.
Inconsistent logging pattern ✅ Passed No inconsistent logging pattern issues detected.
Multi-write without transaction wrapper ✅ Passed No multi-write without transaction wrapper issues detected.
New feature shipped without feature flag ✅ Passed No new feature shipped without feature flag issues detected.
Module placed in the wrong workspace package ✅ Passed No module placed in the wrong workspace package issues detected.
Nonexistent package (not found in registry) ✅ Passed No nonexistent package (not found in registry) issues detected.
Call to function that does not exist ✅ Passed No call to function that does not exist issues detected.
Repetitive boilerplate (duplicated block) ✅ Passed No repetitive boilerplate (duplicated block) issues detected.
Overbroad try/catch swallowing all exceptions ✅ Passed No overbroad try/catch swallowing all exceptions issues detected.
TODO / FIXME on critical path ✅ Passed No todo / fixme on critical path issues detected.
Abstraction defined but never used ✅ Passed No abstraction defined but never used issues detected.
Code style differs from rest of codebase ✅ Passed No code style differs from rest of codebase issues detected.
Insecure training-data / doc-example pattern ✅ Passed No insecure training-data / doc-example pattern issues remaining after verification.
Dead code (defined but never referenced) ✅ Passed No dead code (defined but never referenced) issues detected.
Deprecated API call ✅ Passed No deprecated api call issues detected.
API pattern from wrong library version ✅ Passed No api pattern from wrong library version issues detected.
API endpoint removed ✅ Passed No api endpoint removed issues detected.
HTTP method changed (GET ↔ POST etc.) ✅ Passed No http method changed (get ↔ post etc.) issues detected.
New required field added to request ✅ Passed No new required field added to request issues remaining after verification.
Field removed from response schema ✅ Passed No field removed from response schema issues detected.
Response field type changed ✅ Passed No response field type changed issues detected.
HTTP status code changed ✅ Passed No http status code changed issues detected.
Auth requirement added / removed / changed ✅ Passed No auth requirement added / removed / changed issues detected.
Error response shape changed ✅ Passed No error response shape changed issues detected.
Pagination behaviour changed ✅ Passed No pagination behaviour changed issues detected.
Outbound webhook payload schema changed ✅ Passed No outbound webhook payload schema changed issues detected.
GraphQL field removed without deprecation ✅ Passed No graphql field removed without deprecation issues detected.
GraphQL enum value removed ✅ Passed No graphql enum value removed issues detected.
Vendor API consumer ✅ Passed This PR does not touch call sites of an open vendor API change.
SQL injection ✅ Passed No sql injection issues detected.
Cross-site scripting (XSS) ✅ Passed No cross-site scripting (xss) issues detected.
Path traversal ✅ Passed No path traversal issues detected.
Command injection ✅ Passed No command injection issues detected.
Insecure deserialization ✅ Passed No insecure deserialization issues detected.
Weak cryptography ✅ Passed No weak cryptography issues detected.
Hardcoded secret ✅ Passed No hardcoded secret issues detected.
Insecure randomness for security material ✅ Passed No insecure randomness for security material issues detected.
Unsafe file upload ✅ Passed No unsafe file upload issues detected.
Unsafe CORS configuration ✅ Passed No unsafe cors configuration issues detected.
Unsafe / open redirect ✅ Passed No unsafe / open redirect issues detected.
Unsafe cookie / session settings ✅ Passed No unsafe cookie / session settings issues detected.
Invisible Unicode / Trojan Source characters ✅ Passed No invisible unicode / trojan source characters issues detected.
API key in source ✅ Passed No api key in source detected.
Private key in source ✅ Passed No private key in source detected.
Cloud credential in source ✅ Passed No cloud credential in source detected.
Webhook signing secret in source ✅ Passed No webhook signing secret in source detected.
OAuth client secret in source ✅ Passed No oauth client secret in source detected.
JWT signing secret in source ✅ Passed No jwt signing secret in source detected.
Hardcoded password ✅ Passed No hardcoded password detected.
Dependency has known CVE ✅ Passed No dependency has known cve findings.
Malware package in dependency ✅ Passed No malware package in dependency findings.
Possible typosquat dependency ✅ Passed No possible typosquat dependency findings.
Newly introduced dependency ✅ Passed No newly introduced dependency findings.
Unpinned production dependency ✅ Passed No unpinned production dependency findings.
Deprecated dependency ✅ Passed No deprecated dependency findings.
License allow-list violation ✅ Passed No license allow-list violation findings.
Low dependency grade ✅ Passed No low dependency grade findings.
pull_request_target executes untrusted PR code ✅ Passed No pull_request_target executes untrusted pr code issues detected.
Repository secrets exposed to fork PRs ✅ Passed No repository secrets exposed to fork prs issues detected.
Overbroad GITHUB_TOKEN / pipeline permissions ✅ Passed No overbroad github_token / pipeline permissions issues detected.
Third-party action pinned by mutable tag ✅ Passed No third-party action pinned by mutable tag issues detected.
Shell injection via workflow expression ✅ Passed No shell injection via workflow expression issues detected.
Cache poisoning risk from fork PRs ✅ Passed No cache poisoning risk from fork prs issues detected.
Deployment workflow changed ✅ Passed No deployment workflow changed issues remaining after verification.
Environment protection rule weakened ✅ Passed No environment protection rule weakened issues detected.
Auth middleware removed from route ✅ Passed No auth middleware removed from route issues detected.
Route protection changed (protected → public) ✅ Passed No route protection changed (protected → public) issues detected.
Permission / RBAC check removed ✅ Passed No permission / rbac check removed issues detected.
Required role weakened ✅ Passed No required role weakened issues detected.
Admin-only route exposed to lower privilege ✅ Passed No admin-only route exposed to lower privilege issues detected.
Token validation skipped in middleware chain ✅ Passed No token validation skipped in middleware chain issues detected.
JWT verification weakened or changed ✅ Passed No jwt verification weakened or changed issues detected.
Session expiration / TTL changed ✅ Passed No session expiration / ttl changed issues detected.
Password reset flow changed ✅ Passed No password reset flow changed issues detected.
OAuth callback / redirect handling changed ✅ Passed No oauth callback / redirect handling changed issues remaining after verification.
Webhook endpoint missing signature verification ✅ Passed No webhook endpoint missing signature verification issues detected.
Public route touches private/PII data ✅ Passed No public route touches private/pii data issues detected.
Table dropped (data loss) ✅ Passed No table dropped (data loss) issues detected.
Column dropped (data loss) ✅ Passed No column dropped (data loss) issues detected.
Column type narrowed (truncation risk) ✅ Passed No column type narrowed (truncation risk) issues detected.
Constraint removed ✅ Passed No constraint removed issues detected.
Index dropped ✅ Passed No index dropped issues detected.
NULL → NOT NULL (backfill required) ✅ Passed No null → not null (backfill required) issues detected.
NOT NULL → NULL (constraint relaxed) ✅ Passed No not null → null (constraint relaxed) issues detected.
Column default changed ✅ Passed No column default changed issues detected.
Primary key changed (FK cascade risk) ✅ Passed No primary key changed (fk cascade risk) issues detected.
Foreign key changed ✅ Passed No foreign key changed issues detected.
Unique constraint removed (duplicate risk) ✅ Passed No unique constraint removed (duplicate risk) issues detected.
Enum value removed ✅ Passed No enum value removed issues detected.
Column renamed without compatibility alias ✅ Passed No column renamed without compatibility alias issues detected.
Table renamed without compatibility view ✅ Passed No table renamed without compatibility view issues detected.
Long-locking migration operation ✅ Passed No long-locking migration operation issues detected.
CREATE INDEX without CONCURRENTLY ✅ Passed No create index without concurrently issues detected.
CONCURRENTLY inside a transaction ✅ Passed No concurrently inside a transaction issues detected.
New enum value used in the same transaction ✅ Passed No new enum value used in the same transaction issues detected.
Foreign key added without NOT VALID ✅ Passed No foreign key added without not valid issues detected.
Foreign key column is not indexed ✅ Passed No foreign key column is not indexed issues detected.
Table created without a primary key ✅ Passed No table created without a primary key issues detected.
Timestamp without time zone ✅ Passed No timestamp without time zone issues detected.
Money stored as a float ✅ Passed No money stored as a float issues detected.
Volatile default rewrites the table ✅ Passed No volatile default rewrites the table issues detected.
CHECK constraint added without NOT VALID ✅ Passed No check constraint added without not valid issues detected.
Row-level security policy column is not indexed ✅ Passed No row-level security policy column is not indexed issues detected.
UNIQUE column still allows many NULLs ✅ Passed No unique column still allows many nulls issues detected.
CONCURRENTLY index is not retry-safe ✅ Passed No concurrently index is not retry-safe issues detected.
Frontend performance issue ✅ Passed No additional explanation was reported.
Frontend security issue ✅ Passed No additional explanation was reported.
Frontend correctness issue ✅ Passed No additional explanation was reported.
Accessibility issue ✅ Passed No additional explanation was reported.
Frontend maintainability issue ✅ Passed No additional explanation was reported.
Code correctness issue ✅ Passed No additional explanation was reported.
Resource leak risk ✅ Passed No resource leak risk issues remaining after verification.
Data integrity risk ✅ Passed No additional explanation was reported.
Maintainability issue ✅ Passed No additional explanation was reported.
Redundant alias / duplicate import ✅ Passed No additional explanation was reported.
Redundant type construct ✅ Passed No redundant type construct issues remaining after verification.
Unnecessary type assertion ✅ Passed No unnecessary type assertion issues remaining after verification.
Module smell ✅ Passed No additional explanation was reported.
Dead export (no callers) ✅ Passed No additional explanation was reported.
Code duplication / DRY violation ✅ Passed No additional explanation was reported.
AI authorship ✅ Passed 54% AI-authored (claude:claude-opus-5-5: 54%), 0% human, 46% untracked. (partial coverage — some commits not tracked)
Dependency Auditor ✅ Passed No additional explanation was reported.
Bundle Size Monitor ✅ Passed No bundle size monitor issues remaining after verification.

This comment is updated automatically whenever Autter reviews a new PR revision.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 3 finding(s) as review threads below (🔴 1 · 🟠 2). Each carries a copy-paste AI fix prompt.

return handler(request, env as Env, ctx as Ctx, runtime.rt);
if (!opts.apiKey && !warnedNoKey) {
warnedNoKey = true;
console.warn("[autter-runtime-edge] no apiKey: records are not exported");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 [deterministic] Sensitive data exposure — Risk: 90/100

Sensitive data (credential, internal id, or user secret) flows into a client-bound response or log line without redaction. Blast radius — if this is exploited it cascades to the downstream usage that depends on this file: endpoints POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom; dependent files packages/otlp-ingester/src/fingerprint.ts, packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts.

References:

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • API endpoints: POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom
  • Dependent files: packages/otlp-ingester/src/fingerprint.ts, packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts
🛠 AI fix prompt (copy & paste into your coding agent)
In `packages/runtime-edge/src/index.ts` around line 265, sensitive data (credential, internal id, raw stack trace, or user PII) is sent to a client-bound channel (response body, log line, error payload). Filter the output through a redaction helper / serializer that allow-lists the public-safe fields; for errors, return a generic message + a server-side correlation id and log the trace internally. Blast radius — if this is exploited it cascades to the downstream usage that depends on this file: endpoints `POST /checkout`, `GET /healthz`, `GET /api/users/:id`, `POST /api/checkout`, `POST /api/users/:id`, `GET /boom`; dependent files `packages/otlp-ingester/src/fingerprint.ts`, `packages/otlp-ingester/src/normalize-otlp.ts`, `packages/otlp-ingester/src/sink.ts`, `packages/otlp-ingester/src/server.sink.test.ts`, `packages/otlp-ingester/src/sink.test.ts`, `packages/otlp-ingester/src/llm.ts`, `packages/otlp-ingester/src/index.ts`, `packages/otlp-ingester/src/auth.ts`.

Flagged by Autter security & observability checks.

const app = express();
app.use(autterRequests({ ignore: ["/healthz"] }));
app.use(express.json());
app.get("/healthz", (_req, res) => res.send("ok"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Sensitive data exposure — Risk: 69/100

Sensitive data (credential, internal id, or user secret) flows into a client-bound response or log line without redaction. Blast radius — if this is exploited it cascades to the downstream usage that depends on this file: endpoints POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom; dependent files packages/otlp-ingester/src/fingerprint.ts, packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts.

References:

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • API endpoints: POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom
  • Dependent files: packages/otlp-ingester/src/fingerprint.ts, packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts
🛠 AI fix prompt (copy & paste into your coding agent)
In `packages/runtime-node/test/requests.test.mjs` around line 48, sensitive data (credential, internal id, raw stack trace, or user PII) is sent to a client-bound channel (response body, log line, error payload). Filter the output through a redaction helper / serializer that allow-lists the public-safe fields; for errors, return a generic message + a server-side correlation id and log the trace internally. Blast radius — if this is exploited it cascades to the downstream usage that depends on this file: endpoints `POST /checkout`, `GET /healthz`, `GET /api/users/:id`, `POST /api/checkout`, `POST /api/users/:id`, `GET /boom`; dependent files `packages/otlp-ingester/src/fingerprint.ts`, `packages/otlp-ingester/src/normalize-otlp.ts`, `packages/otlp-ingester/src/sink.ts`, `packages/otlp-ingester/src/server.sink.test.ts`, `packages/otlp-ingester/src/sink.test.ts`, `packages/otlp-ingester/src/llm.ts`, `packages/otlp-ingester/src/index.ts`, `packages/otlp-ingester/src/auth.ts`.

Flagged by Autter security & observability checks.

Comment thread packages/otlp-ingester/src/sink.ts Outdated
...(traceId ? { traceId } : {}),
...(route ? { route } : {}),
...(method ? { method } : {}),
...(statusCode ? { statusCode } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [ai] statusCode 0 dropped by truthiness guard in sinkOccurrence — Risk: 50/100

sinkOccurrence emits the optional request-context fields with truthiness guards, and ...(statusCode ? { statusCode } : {}) treats a valid numeric status of 0 as absent. Browsers report status: 0 for opaque (no-cors) responses and failed/blocked requests, and the previous ...o spread passed that value through, so sink consumers now lose the request context for exactly those failure cases. Guard on statusCode !== undefined && statusCode !== null (or statusCode != null) instead of truthiness; the string guards on traceId/route/method/errorCode/etc. are correct as-is.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Functions/symbols: SinkForwarder.pump, SinkForwarder.enforceBounds, SinkForwarder.scheduleWake, SinkForwarder.untrack, SinkForwarder.maxBufferedBytes, SinkForwarder.send, normalizeTraces, normalizeMetrics
  • API endpoints: POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom
  • Dependent files: packages/otlp-ingester/src/fingerprint.ts, packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts, packages/otlp-ingester/src/normalize-browser.test.ts
  • Scopes: @autter/otlp-ingester
🛠 AI fix prompt (copy & paste into your coding agent)
In `sinkOccurrence`, include `statusCode` whenever it is not null or undefined rather than testing its truthiness. Add a test confirming that a status code of 0 is preserved in the serialized occurrence. Blast radius — if this defect reaches production it can fail the downstream usage that depends on this file: functions `SinkForwarder.pump`, `SinkForwarder.enforceBounds`, `SinkForwarder.scheduleWake`, `SinkForwarder.untrack`, `SinkForwarder.maxBufferedBytes`, `SinkForwarder.send`, `normalizeTraces`, `normalizeMetrics`; endpoints `POST /checkout`, `GET /healthz`, `GET /api/users/:id`, `POST /api/checkout`, `POST /api/users/:id`, `GET /boom`; scopes `@autter/otlp-ingester`; dependent files `packages/otlp-ingester/src/fingerprint.ts`, `packages/otlp-ingester/src/normalize-otlp.ts`, `packages/otlp-ingester/src/server.sink.test.ts`, `packages/otlp-ingester/src/sink.test.ts`, `packages/otlp-ingester/src/llm.ts`, `packages/otlp-ingester/src/index.ts`, `packages/otlp-ingester/src/auth.ts`, `packages/otlp-ingester/src/normalize-browser.test.ts`.

Flagged by Autter security & observability checks.

@autter-dev

autter-dev Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

🧭 PR hygiene & process suggestions

Autter has 5 suggestion(s) about the shape of this PR (size, scope, reviewability). These are process guidance — not code defects — so they are consolidated here instead of posted as inline comments on individual files.

🟠 PR changes too many files — split it — Risk: 75/100

This PR changes 96 files (limit 25 lowered from 50 due to high-impact files). Highest-impact files: packages/otlp-ingester/src/types.ts, packages/otlp-ingester/src/config.ts, packages/otlp-ingester/src/fingerprint.ts. Consider splitting this PR by area: packages (74 files), docs (8 files), examples (6 files), (root) (5 files), .github (2 files), scripts/ensure-built.mjs (1 file). Blast radius — if this PR's size obscures a regression here it cascades: functions RuntimeOccurrenceInput, normalizeTraces, normalizeMetrics, instrumentLlmClient, loadConfig, sanitizeRuntimeContext, RuntimeLogContextValue, RuntimeLogRecord; endpoints POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom; scopes @autter/otlp-ingester; dependent files packages/otlp-ingester/src/fingerprint.ts, packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts.

References:

🛠 AI fix prompt (copy & paste into your coding agent)
Split this pull request so each child PR contains a focused, independently reviewable change.
Start by separating `packages/otlp-ingester/src/types.ts` into its own PR if it stands alone, or group it with siblings that share its scope/owner.
Goal: each child PR should change well under 50 files and be reviewable in one sitting.
Concretely: extract independent commits with `git cherry-pick`, push each as its own branch, and open separate PRs.

References:
https://docs.rs/crate/godharness-core/latest/source/standards/small-reviewable-changes.md
https://go.dev/wiki/CodeReview

🟠 PR mixes refactor and behavior change — Risk: 63/100

The PR moves redaction into the new runtime-core package, leaving runtime-node/src/redact.ts as a re-export. That is a pure refactor. The same PR adds request summaries, coded errors, a new code-v1 fingerprint that regroups issues, and sink payload changes that omit fields when empty. It also reworks logger.ts (632 additions, 358 deletions). The refactor is mixed with behavior changes in high-fan-in ingester files such as types.ts, fingerprint.ts and config.ts. packages/runtime-node/src/redact.ts and packages/runtime-core/src/redact.ts are the files contributing most to the mix. Split into stacked PRs. (1) A no-behavior refactor that extracts runtime-core, including redact and shared helpers, with the golden test proving unchanged output. (2) Ingester changes: error-field lifting, the code-v1 fingerprint, and the sink payload changes. (3) Node, edge and browser SDK features for request summaries and coded errors. (4) Docs and release tooling. Blast radius — if the refactor introduces a regression the behavior change masks it on: endpoints POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom; dependent files packages/otlp-ingester/src/fingerprint.ts, packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts.

References:

🛠 AI fix prompt (copy & paste into your coding agent)
Split this PR so refactors land separately from behavior changes.
Pure-refactor PRs should preserve behavior (no test changes beyond renames). Behavior-change PRs should focus on a single new capability.
Start by extracting `packages/runtime-node/src/redact.ts`'s refactor portion (or its behavior portion, whichever is smaller) into its own PR.

References:
https://pkg.go.dev/goforge.dev/tbd/v2
https://docs.rs/crate/git-next/latest

🟡 Requirement only partially built — Risk: 38/100

The PR description asked for: "SDK emits per-request summaries with x-request-id, runtimeContext, RuntimeError/defineRuntimeErrors/autterErrorResponse, carriers, AI usage rollup, initAutterLogging, and the new runtime-edge package", but the diff only partially delivers it. The SDK source files are in the omitted portion of the diff, so none of this could be verified. Only the ingester side is visible.

🛠 AI fix prompt (copy & paste into your coding agent)
Implement "SDK emits per-request summaries with x-request-id, runtimeContext, RuntimeError/defineRuntimeErrors/autterErrorResponse, carriers, AI usage rollup, initAutterLogging, and the new runtime-edge package" as the PR description asked, or confirm it is intentionally deferred and record that in the PR description.

🟡 Change not tied to any stated requirement — Risk: 20/100

Flagged in 2 places: packages/otlp-ingester/src/config.ts:96, packages/otlp-ingester/src/normalize-browser.ts:84.

No stated requirement asked for this: A per-tenant promotion-lookup rate limit (PROMOTION_LOOKUPS_PER_MINUTE) for /v1/logs promotion is added. The visible intent text does not mention it, though it may support log promotion.. Confirm it belongs in this PR rather than a separate change.

🛠 AI fix prompt (copy & paste into your coding agent)
Confirm this change is in scope for the PR's stated intent. If it is unrelated, split it into its own PR; if it is required, note why in the PR description.

Flagged by Autter PR-hygiene checks.


💡 2 suggestion(s) — conventions, hardening and hygiene, not defects

Inline comments are reserved for concrete defects. These are things worth knowing that the diff does not prove wrong — a missing hardening layer, a convention the repo usually follows, a file that historically changes alongside one you touched. Skim, adopt what fits, ignore the rest.

  • 🟠 packages/runtime-next/src/index.ts usually changes with this file (risk 55/100) — packages/runtime-node/src/index.ts:47
  • 🟡 High branching in sinkOccurrence (risk 15/100) — packages/otlp-ingester/src/sink.ts:420
⚠️ 47 unconfirmed finding(s) — flagged by a detector but not proven by Autter's verification pass

Inline comments are reserved for findings that survived verification. These are plausible but could not be confirmed from the available context, so they are listed here as FYIs instead — review the ones that look real to you.

  • 🟠 Generic placeholder identifier in production logic (risk 64/100) — packages/runtime-node/src/logger.ts:360 — not individually verified (per-bundle verification cap reached)
  • 🟠 Generic placeholder identifier in production logic (risk 56/100) — packages/otlp-ingester/src/error-fields.ts:44 — not individually verified (per-bundle verification cap reached)
  • 🟠 Generic placeholder identifier in production logic (risk 56/100) — packages/runtime-core/src/context.ts:117 — not individually verified (per-bundle verification cap reached)
  • 🟠 Generic placeholder identifier in production logic (risk 56/100) — packages/runtime-edge/src/index.ts:232 — not individually verified (per-bundle verification cap reached)
  • 🟠 Generic placeholder identifier in production logic (risk 56/100) — packages/runtime-node/src/enrichers.ts:18 — not individually verified (per-bundle verification cap reached)
  • 🟠 Generic placeholder identifier in production logic (risk 56/100) — packages/runtime-node/src/requests.ts:208 — not individually verified (per-bundle verification cap reached)
  • 🟡 Runtime error risk (risk 49/100) — packages/runtime-core/src/redact.ts:197 — not individually verified (per-bundle verification cap reached)
  • 🟡 Runtime error risk (risk 49/100) — packages/runtime-next/src/server.ts:158 — not individually verified (per-bundle verification cap reached)
  • 🟡 Generic placeholder identifier in production logic (risk 43/100) — packages/runtime-core/src/carrier.ts:26 — not individually verified (per-bundle verification cap reached)
  • 🟡 Generic placeholder identifier in production logic (risk 43/100) — packages/runtime-core/src/fingerprint-code.ts:20 — not individually verified (per-bundle verification cap reached)
  • 🟡 Generic placeholder identifier in production logic (risk 43/100) — packages/runtime-core/src/redact.ts:205 — not individually verified (per-bundle verification cap reached)
  • 🟡 Generic placeholder identifier in production logic (risk 43/100) — packages/runtime-core/src/request.ts:19 — not individually verified (per-bundle verification cap reached)
  • 🟡 Generic placeholder identifier in production logic (risk 43/100) — packages/runtime-core/src/user-agent.ts:17 — not individually verified (per-bundle verification cap reached)
  • 🟡 Generic placeholder identifier in production logic (risk 43/100) — scripts/ensure-built.mjs:23 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — CHANGELOG.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — docs/CONTINUOUS-DETECTION.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — docs/EXTERNAL-SOURCES.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — docs/GETTING-STARTED.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — docs/INTEGRATIONS.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — docs/OPERATION-LOGGING.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — docs/PLAN.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — examples/edge-worker/src/index.js:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — examples/express-app/server.js:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — package-lock.json:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/otlp-ingester/README.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/otlp-ingester/src/fingerprint.ts:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/otlp-ingester/src/migrations.ts:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/otlp-ingester/src/server.ts:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/otlp-ingester/src/sink.ts:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-browser/README.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-core/package.json:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-core/src/fingerprint-code.ts:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-core/src/otlp-logs.ts:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-core/src/user-agent.ts:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-edge/package.json:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-next/README.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-next/test/next.test.mjs:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-node/README.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-node/src/errors.ts:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-node/src/index.ts:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-node/src/llm-instrument.ts:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-node/test/golden.test.mjs:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-node/test/hook-mode.test.mjs:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — packages/runtime-node/test/logging-only.test.mjs:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — README.md:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Unattested changes (risk 30/100) — scripts/ensure-built.mjs:1 — not individually verified (per-bundle verification cap reached)
  • 🟡 Inconsistent error handling (risk 20/100) — packages/runtime-core/src/fingerprint-code.ts:19 — The flagged line is a precondition guard: throw new Error("codeFingerprint requires WebCrypto") fires only when globalThis.crypto.subtle is missing. That never happens on Node >=20, and invalid codes return null before the check. Nothing provided shows a repo-wide typed-error convention that this breaks; config.ts, for example, logs and falls back rather than throwing typed errors. The only re

🔎 35 low-signal finding(s) (test-file conventions, hardening advice on internal routes, packages the registry knows) were kept off this PR — see the Autter dashboard.

@autter-dev

autter-dev Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Release Notes Curator

Impact: minor. This adds new user-facing capabilities: request summaries, coded errors, an edge SDK and test helpers. The changes are additive, and the wire payload stays at version: 1.

Changelog entry

Runtime 1.5.0: request summaries, coded errors and an edge SDK

  • Request summaries. Each request now produces one summary record with its outcome, so you can see how a request ended without piecing together individual log lines.
  • Coded errors. You can give errors a stable code such as billing.declined, plus an optional cause, fix and docs link. Errors with the same code group into a single issue across server, browser and external sources. You can also mark an error as an expected business failure, such as a card decline. It is recorded but does not page anyone.
  • Edge runtimes. A new edge SDK brings the same logging and error reporting to Workers, Vercel Edge, Deno and Bun.
  • Browser SDK 1.4.0. Errors with a code are picked up automatically. A new autterErrorFromResponse() turns a failed fetch response into a coded error. Failed requests now carry the server's request id, which links a browser failure to its server-side summary.
  • Testing. @autter/runtime-node/testing lets you capture and assert on runtime records in tests, with no network or ingester.
  • Heads-up: the browser package is now ESM-only. The CommonJS build is gone. Bundlers and Node versions that support require(esm) keep working.

Custom agent · runs after review · configured in Autter

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 11 finding(s) as review threads below (🟠 11). Each carries a copy-paste AI fix prompt.

});
const [table] = await rows.json<{ engine_full: string }>();
if (!table) return;
const current = /\bTTL\b[^]*?toIntervalDay\((\d+)\)/.exec(table.engine_full);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/correctness/noEmptyCharacterClassInRegex — Risk: 55/100

The regular expression includes this negated empty character class.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/correctness/noEmptyCharacterClassInRegex` issue at packages/otlp-ingester/src/clickhouse.ts:265: The regular expression includes this negated empty character class.

Flagged by Autter security & observability checks.

for (const ok of ["billing.declined", "a", "a.b.c.d", "inventory.reservation_timeout", "x1.y_2"]) {
assert.equal(validErrorCode(ok), ok);
}
for (const bad of ["ECONNRESET", "Billing.declined", "1abc", "a.b.c.d.e", "a..b", "a.", "a-b", "user.42@x", "", 42, null, "a" + "b".repeat(80)]) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/style/useTemplate — Risk: 55/100

Template literals are preferred over string concatenation.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/style/useTemplate` issue at packages/otlp-ingester/src/coded-errors.test.ts:99: Template literals are preferred over string concatenation.

Flagged by Autter security & observability checks.

]),
);
assert.equal(fields.errorCode, "billing.declined");
assert.equal(fields.why!.length, 1000);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/style/noNonNullAssertion — Risk: 55/100

Forbidden non-null assertion.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/style/noNonNullAssertion` issue at packages/otlp-ingester/src/coded-errors.test.ts:118: Forbidden non-null assertion.

Flagged by Autter security & observability checks.

if (rawLink) declared.link = rawLink;
const rawRoute =
typeof attributes["http.route"] === "string"
? attributes["http.route"].split(/[?#]/)[0]!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/style/noNonNullAssertion — Risk: 55/100

Forbidden non-null assertion.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/style/noNonNullAssertion` issue at packages/otlp-ingester/src/logs.ts:176: Forbidden non-null assertion.

Flagged by Autter security & observability checks.

): { traceId: string; spanId: string; sampled: boolean } | null {
if (typeof value !== "string") return null;
const match = TRACEPARENT.exec(value.trim());
if (!match || /^0+$/.test(match[1]!) || /^0+$/.test(match[2]!)) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/style/noNonNullAssertion — Risk: 55/100

Forbidden non-null assertion.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/style/noNonNullAssertion` issue at packages/runtime-core/src/carrier.ts:37: Forbidden non-null assertion.

Flagged by Autter security & observability checks.

const regexes = patterns.map((pattern) => {
let source = "";
for (let i = 0; i < pattern.length; i++) {
const char = pattern[i]!;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/style/noNonNullAssertion — Risk: 55/100

Forbidden non-null assertion.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/style/noNonNullAssertion` issue at packages/runtime-core/src/request.ts:57: Forbidden non-null assertion.

Flagged by Autter security & observability checks.

test("the queue is bounded per isolate", async () => {
let calls = 0;
const worker = withAutter(
{ apiKey: "k", service: "edge-api", maxQueue: 3, fetch: async () => (calls++, new Response("", { status: 503 })) },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/style/noCommaOperator — Risk: 55/100

The comma operator is disallowed.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/style/noCommaOperator` issue at packages/runtime-edge/test/edge.test.mjs:127: The comma operator is disallowed.

Flagged by Autter security & observability checks.

const requests = [];
const server = createServer((req, res) => {
let body = "";
req.on("data", (chunk) => (body += chunk));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/suspicious/noAssignInExpressions — Risk: 55/100

The assignment should not be in an expression.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/suspicious/noAssignInExpressions` issue at packages/runtime-node/test/operations.test.mjs:26: The assignment should not be in an expression.

Flagged by Autter security & observability checks.

try {
let carrier;
let producerId;
let producerTrace;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/style/useConst — Risk: 55/100

This let declares a variable that is only assigned once.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/style/useConst` issue at packages/runtime-node/test/operations.test.mjs:92: This let declares a variable that is only assigned once.

Flagged by Autter security & observability checks.

test("fastify plugin (structural, no dependency) summarises requests", async () => {
const runtime = captureRuntime();
const hooks = {};
const fastify = { addHook: (name, fn) => (hooks[name] = fn) };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/suspicious/noAssignInExpressions — Risk: 55/100

The assignment should not be in an expression.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/suspicious/noAssignInExpressions` issue at packages/runtime-node/test/requests.test.mjs:209: The assignment should not be in an expression.

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 5 finding(s) as review threads below (🟡 5). Each carries a copy-paste AI fix prompt.

Comment thread CHANGELOG.md

## [1.5.0] - 2026-10-08

Packages: `otlp-ingester` 1.5.0, `runtime-node` 1.5.0, `runtime-next` 1.5.0, `runtime-browser` 1.4.0, new `runtime-edge` 1.0.0. Additive: 1.4.0 code keeps working unchanged (golden-tested).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] markdownlint: MD013 — Risk: 30/100

Line length: Expected: 80; Actual: 189

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the markdownlint `MD013` issue at CHANGELOG.md:5: Line length: Expected: 80; Actual: 189

Flagged by Autter security & observability checks.

Comment thread docs/ARCHITECTURE.md
| `runtime_error_occurrences` | MergeTree | `(org_id, repository_id, fingerprint, occurred_at)` | 14 d |
| `runtime_spans` | MergeTree | `(org_id, repository_id, trace_id, started_at)` | 7 d |
| `runtime_logs` | ReplacingMergeTree | `(org_id, repository_id, occurred_at, event_id)` | 14 d |
| `runtime_logs` | ReplacingMergeTree | `(org_id, repository_id, occurred_at, event_id)` | `LOG_TTL_DAYS` (14 d) |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] markdownlint: MD013 — Risk: 30/100

Line length: Expected: 80; Actual: 114

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the markdownlint `MD013` issue at docs/ARCHITECTURE.md:47: Line length: Expected: 80; Actual: 114

Flagged by Autter security & observability checks.

Comment thread packages/runtime-browser/src/index.ts Outdated
}

function addContext(event: BrowserEvent, extra: Record<string, unknown>): void {
if (Object.keys(extra).length > 0) event.context = { ...(event.context || {}), ...extra };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] oxlint: unicorn(no-useless-fallback-in-spread) — Risk: 30/100

packages/runtime-browser/src/index.ts:175:55: Empty fallbacks in spreads are unnecessary

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the oxlint `unicorn(no-useless-fallback-in-spread)` issue at packages/runtime-browser/src/index.ts:175: packages/runtime-browser/src/index.ts:175:55: Empty fallbacks in spreads are unnecessary

Flagged by Autter security & observability checks.

Comment thread packages/runtime-core/src/redact.ts Outdated
* masked wholesale, before anything is exported.
*/

type AttrValue =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] oxlint: eslint(no-unused-vars) — Risk: 30/100

packages/runtime-core/src/redact.ts:12:6: Type alias 'AttrValue' is declared but never used.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the oxlint `eslint(no-unused-vars)` issue at packages/runtime-core/src/redact.ts:12: packages/runtime-core/src/redact.ts:12:6: Type alias 'AttrValue' is declared but never used.

Flagged by Autter security & observability checks.

Comment thread packages/runtime-core/src/redact.ts Outdated
options?: boolean | RedactOptions,
): (attributes?: Attributes | null) => Attributes {
if (options === false) {
return (attributes) => ({ ...(attributes ?? {}) });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] oxlint: unicorn(no-useless-fallback-in-spread) — Risk: 30/100

packages/runtime-core/src/redact.ts:405:29: Empty fallbacks in spreads are unnecessary

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the oxlint `unicorn(no-useless-fallback-in-spread)` issue at packages/runtime-core/src/redact.ts:405: packages/runtime-core/src/redact.ts:405:29: Empty fallbacks in spreads are unnecessary

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 3 finding(s) as review threads below (🔴 1 · 🟠 2). Each carries a copy-paste AI fix prompt.

Comment thread package-lock.json
"node": ">=20"
}
},
"examples/edge-worker": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 [deterministic] Vulnerable dependency: proxy-addr@2.0.7 — CVE-2026-90711 — Risk: 85/100

proxy-addr@2.0.7 (npm) is affected by CVE-2026-90711 (GHSA-jqcg-44mw-7w3h) — critical severity. proxy-addr vulnerable to IP spoofing via IPv4-mapped IPv6 trust subnet

🛠 AI fix prompt (copy & paste into your coding agent)
Upgrade `proxy-addr` past the version affected by CVE-2026-90711, or apply the advisory's mitigation. Details: https://osv.dev/GHSA-jqcg-44mw-7w3h.

Flagged by Autter security & observability checks.

Comment thread CHANGELOG.md
@@ -1,5 +1,41 @@
# Changelog

## [1.5.0] - 2026-10-08

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Possible PII in source: PHONE_NUMBER — Risk: 72/100

Possible PHONE_NUMBER on this line. Committing real personal data (or leaking it in code/config/logs) is a privacy risk.

🛠 AI fix prompt (copy & paste into your coding agent)
Remove the PHONE_NUMBER value at CHANGELOG.md:3. If it's test data, use an obviously-fake placeholder; if it's real, purge it and rotate anything exposed.

Flagged by Autter security & observability checks.

["autter.error.fix", "Email jane@example.com for help"],
["autter.error.link", "https://user:pw@docs.example.com/payments?token=1#declined"],
["autter.error.expected", "true"],
["autter.request.id", "req_0123456789"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Possible PII in source: PHONE_NUMBER — Risk: 72/100

Possible PHONE_NUMBER on this line. Committing real personal data (or leaking it in code/config/logs) is a privacy risk.

🛠 AI fix prompt (copy & paste into your coding agent)
Remove the PHONE_NUMBER value at packages/otlp-ingester/src/coded-errors.test.ts:114. If it's test data, use an obviously-fake placeholder; if it's real, purge it and rotate anything exposed.

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 2 finding(s) as review threads below (🟡 2). Each carries a copy-paste AI fix prompt.

*
* `event.waitUntil` (NextFetchEvent) is used for delivery automatically.
*/
export * from "@autter/runtime-edge";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Build failed: @autter/runtime-next — Risk: 40/100

npm run build could not verify the @autter/runtime-next build in packages/runtime-next — this is not evidence that this PR breaks it.

What happened: The build for @autter/runtime-next fails because TypeScript cannot resolve modules @autter/runtime-edge and @autter/runtime-node in src/edge.ts and src/server.ts.

How to fix: This looks like an incomplete install or missing lockfile. Try running npm install across the workspace or build the dependent packages before attempting to build runtime-next.

Build output
 Cannot find module '@autter/runtime-edge' or its corresponding type declarations.

Error: error occurred in dts build
    at Worker.<anonymous> (/tmp/autter-agentic-PpMD8i/node_modules/tsup/dist/index.js:1545:26)
    at Worker.emit (node:events:514:28)
    at MessagePort.<anonymous> (node:internal/worker:350:14)
    at [nodejs.internal.kHybridDispatch] (node:internal/event_target:848:20)
    at MessagePort.<anonymous> (node:internal/per_context/messageport:23:28)
src/server.ts(47,8): error TS2307: Cannot find module '@autter/runtime-node' or its corresponding type declarations.
src/server.ts(92,8): error TS2307: Cannot find module '@autter/runtime-node' or its corresponding type declarations.
src/server.ts(130,8): error TS2307: Cannot find module '@autter/runtime-node' or its corresponding type declarations.

npm error Lifecycle script `build` failed with error:
npm error code 1
npm error path /tmp/autter-agentic-PpMD8i/packages/runtime-next
npm error workspace @autter/runtime-next@1.5.0
npm error location /tmp/autter-agentic-PpMD8i/packages/runtime-next
npm error command failed
npm error command sh -c node -e "require('node:fs').rmSync('dist',{recursive:true,force:true})" && tsup
🛠 AI fix prompt (copy & paste into your coding agent)
The @autter/runtime-next build (`npm run build` in `packages/runtime-next`) fails. The build for @autter/runtime-next fails because TypeScript cannot resolve modules @autter/runtime-edge and @autter/runtime-node in src/edge.ts and src/server.ts. Fix: This looks like an incomplete install or missing lockfile. Try running npm install across the workspace or build the dependent packages before attempting to build runtime-next.

Flagged by Autter security & observability checks.

* Works with or without initAutterServer/initAutterLogging; before init it
* replaces the default console output instead of adding to it.
*/
import { isSummary, type RuntimeEvent, type RuntimeOutcome } from "@autter/runtime-core";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Build failed: @autter/runtime-node — Risk: 40/100

npm run build could not verify the @autter/runtime-node build in packages/runtime-node — this is not evidence that this PR breaks it.

What happened: The build for @autter/runtime-node fails because it cannot resolve @autter/runtime-core, specifically reporting that ./dist/index.js is missing on the file system.

How to fix: This looks like an incomplete install or missing lockfile. Ensure @autter/runtime-core is built and available in node_modules, or run npm install to refresh the workspace dependencies.

Build output
mark the path "@autter/runtime-core" as external to exclude it from the bundle, which will remove this error and leave the unresolved path in the bundle.

✘ [ERROR] Could not resolve "@autter/runtime-core"

    src/logging-only.ts:7:32:
      7 │ import { errorAttributes } from "@autter/runtime-core";
        ╵                                 ~~~~~~~~~~~~~~~~~~~~~~

  The module "./dist/index.js" was not found on the file system:

    ../../node_modules/@autter/runtime-core/package.json:14:13:
      14 │       "import": "./dist/index.js"
         ╵                 ~~~~~~~~~~~~~~~~~

  You can mark the path "@autter/runtime-core" as external to exclude it from the bundle, which will remove this error and leave the unresolved path in the bundle.

✘ [ERROR] Could not resolve "@autter/runtime-core"

    src/redact.ts:11:7:
      11 │ } from "@autter/runtime-core";
         ╵        ~~~~~~~~~~~~~~~~~~~~~~

  The module "./dist/index.js" was not found on the file system:

    ../../node_modules/@autter/runtime-core/package.json:14:13:
      14 │       "import": "./dist/index.js"
         ╵                 ~~~~~~~~~~~~~~~~~

  You can mark the path "@autter/runtime-core" as external to e…
🛠 AI fix prompt (copy & paste into your coding agent)
The @autter/runtime-node build (`npm run build` in `packages/runtime-node`) fails. The build for @autter/runtime-node fails because it cannot resolve @autter/runtime-core, specifically reporting that ./dist/index.js is missing on the file system. Fix: This looks like an incomplete install or missing lockfile. Ensure @autter/runtime-core is built and available in node_modules, or run npm install to refresh the workspace dependencies.

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 3 finding(s) as review threads below (🟠 2 · 🟡 1). Each carries a copy-paste AI fix prompt.

// reach grouping through the trace path. The exception: logger-only /
// edge SDKs mark span-less error records `autter.capture.mode = "log"`,
// and those are promoted to occurrences here — see log-promotion.ts.
const promoted = await promoteLogs(ctx, logs);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [ai] Log promotion adds an expensive scan without a stricter route limit — Risk: 50/100

POST /v1/logs still requires a valid server ingest key and is protected by the shared per-key limit, but this change adds a ClickHouse DISTINCT trace lookup and occurrence/metric writes to that endpoint. The only server-key budget is the generic 300 requests/minute default; each request may be up to 1 MiB and normalize up to 2,000 records. For promotion candidates, the lookup range is derived from the submitted event timestamps and can span the stored occurrence interval, while the query filters by trace IDs rather than a dedicated trace-ID ordering key. A caller with a valid key can therefore amplify load on the shared ClickHouse store with many large batches and wide lookup ranges. Add a substantially tighter per-key budget for this expensive promotion path and bound the permitted timestamp span/query work.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: packages/otlp-ingester/src/server.ts, packages/otlp-ingester/src/config.ts, packages/otlp-ingester/src/logs.ts, packages/otlp-ingester/src/log-promotion.ts, packages/otlp-ingester/src/clickhouse.ts
🛠 AI fix prompt (copy & paste into your coding agent)
Add a dedicated, substantially tighter per-key rate limit for /v1/logs promotion work, and constrain the dedupe lookup to a bounded event-time window (reject, clamp, or skip lookup for batches whose candidate timestamps span too far). Preserve the existing ingest-key authentication and tenant scoping.

Flagged by Autter security & observability checks.

Comment thread packages/runtime-edge/src/index.ts Outdated
const body = JSON.stringify(
buildOtlpLogsRequest(
{
service: opts.service,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [ai] Per-isolate queue can send records using another request's credentials — Risk: 50/100

The exporter queue is shared across requests, but queued entries contain only encoded records; delivery builds the resource and authorization header from the options of whichever request happens to flush the queue. With the supported env-dependent options callback, request A can enqueue records while its options have no apiKey (delivery returns early at line 201), then request B can flush those records using B's service/environment and apiKey. This can export records that were configured not to export and, when keys select different tenants, store A's telemetry under B's tenant. Keep queued records partitioned/tagged by their original destination configuration, or discard records when export is disabled.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: packages/runtime-edge/src/index.ts
🛠 AI fix prompt (copy & paste into your coding agent)
Preserve the resolved destination configuration with each queued record/batch and only send records using that configuration; alternatively do not queue records when apiKey is absent. Add coverage for two sequential requests with differing env-derived service/apiKey settings.

Flagged by Autter security & observability checks.

const out: Record<string, unknown> = {};
if (!error || typeof error !== "object") return out;
const e = error as Record<string, unknown>;
if (typeof e.code === "string" && e.code.length <= 80 && CODE_RE.test(e.code)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [ai] Throwing error metadata getters abort browser exception capture — Risk: 40/100

The new errorContext path directly reads duck-typed fields such as code, why, fix, link, expected, and requestId without guarding property access. An Error subclass or Proxy whose code getter throws therefore causes captureException to throw at this line before enqueue(event, true), so callers do not report the original exception; the changed React boundary calls this public API directly from componentDidCatch, and the global error/rejection handlers also call errorContext before enqueueing. This removes the prior guarantee that captureException could report an Error without inspecting optional metadata.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: packages/runtime-next/src/error-boundary.ts, packages/runtime-browser/src/index.ts
🛠 AI fix prompt (copy & paste into your coding agent)
Read each optional metadata field through a guarded helper (or wrap the metadata extraction as a whole) so throwing getters are ignored and captureException always reaches enqueue(event, true). Apply the same fail-open behavior to global error and unhandled-rejection listeners.

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 1 finding(s) as review threads below (🟠 1). Each carries a copy-paste AI fix prompt.

Comment thread packages/runtime-core/src/redact.ts Outdated
// of the value survives — useful context like a stack frame stays readable).
const PRIVATE_KEY_BLOCK_RE =
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g;
const EMAIL_VALUE_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [ai] ReDoS in shared value-redaction regexes (email and URL-credential scrubbers) blocks the event loop on attacker-controlled log, error and context strings — Risk: 55/100

The shared redaction module applies an unbounded email regex and an unbounded URL-credential regex to every string that flows into request summaries, log records, captured exceptions and operation context. A string of tens of kilobytes with no @​ makes these regexes take seconds of synchronous CPU time. A single request can therefore freeze the Node process or edge isolate, and the stall blocks all other requests.

redactString() runs EMAIL_VALUE_RE and URL_CREDENTIALS_RE over every string value. boundContext() and mergeContext() call it before the 16 KB truncation, so the full-length input is scanned.

const EMAIL_VALUE_RE = /[A-Z0-9._%+-]+@​[A-Z0-9.-]+\.[A-Z]{2,}/gi;
...
const URL_CREDENTIALS_RE =
	/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/:@​]+:[^\s/@​]+@​/gi;

The email pattern uses an unbounded leading class [A-Z0-9._%+-]+. For each start offset the engine consumes the whole run of local-part characters and then fails to find @​, which gives O(n²) behaviour on inputs such as "a".repeat(n) or "a.".repeat(n). The URL pattern has the same shape: an unbounded scheme [a-z0-9+.-]* and unbounded userinfo classes, so inputs like "xox" + "b-".repeat(n) backtrack heavily.

Reachability in the new code: the new request wide-event path (autterRequests, withRuntimeRequest, autterFastify, withAutter) routes runtimeContext.set, inline logs, error messages (captureException, exception.message, message) and operation names through boundContext and makeRedactor. These calls run synchronously on the request path, in emit() in logger.ts and in record() in the edge buildRuntime. The edge SDK also invokes the redactor on the thrown error's message.

  1. Bundle runtime-core, or run any app using autterRequests with runtimeContext.set({ search: req.body }).
  2. Send a POST with a 50,000-character body of the letter a.
  3. Concurrently poll an unrelated endpoint and observe that its latency equals the redaction time (about 4 s).
  4. Alternatively, call boundContext({m: "a".repeat(50000)}, makeRedactor(true)) directly and time it.

Evidence:
Measured on Node 24 with the repo sources bundled:

50 KB body POST /search -&gt; attack request 4162 ms
unrelated GET /health2 during attack -&gt; max latency 4161 ms
edge withAutter throw with 20 KB message -&gt; 3861 ms
Old EMAIL_VALUE_RE on 40 KB 'a'*n -&gt; 1867 ms (n=20000: 464 ms, ~4x per doubling = quadratic)
Old URL_CREDENTIALS_RE on 'xox'+'b-'*25000 -&gt; ~2.3 s
After fix: 50 KB 'a' -&gt; 30 ms, 'a.'*25000 -&gt; 35 ms

The MAX_REDACTION_WORK and depth limits bound the number of nodes, but not the length of a single string. The 16 KB cap in boundContext is applied after redaction. The framework body-size limit is the only mitigating factor; Express text/json defaults allow 100 KB, enough for a ~12 s stall. The same regexes existed in runtime-node 1.4.0, but the diff moves them to shared core and newly routes request summaries, error messages and edge handler errors through them on the request path. No timeout or length pre-check was found on this path.

Proof of concept:

// Express + autterRequests (built from the repo sources)
app.use(autterRequests());
app.use(express.text({ limit: "1mb" }));
app.post("/search", (req, res) =&gt; { runtimeContext.set({ search: req.body }); res.send("ok"); });
app.get("/health2", (q, s) =&gt; s.send("hi"));
// attacker:
await fetch(base + "/search", { method: "POST", headers: {"content-type": "text/plain"}, body: "a".repeat(50000) });
// victim poll during the attack
// Result: attack request 4162 ms; unrelated GET /health2 max latency 4161 ms

// Edge:
const h = withAutter({service:"x"}, async (req) =&gt; { throw new Error("bad input: " + await req.text()); });
await h(new Request("http://x/y", {method:"POST", body:"a".repeat(20000)})).catch(()=&gt;{}); // ~3.9 s
🛠 AI fix prompt (copy & paste into your coding agent)
Bound the quantifiers in the email and URL-credential patterns so matching is linear (local part 64, domain 255, scheme 32, userinfo 256). Truncate each string to a fixed maximum length before running value patterns. Apply the same bounds to the equivalent email scrubbing patterns in the ingester (`context.ts`, `normalize-browser.ts`, `error-fields.ts`), where an attacker holding a public client key can send 20-40 KB strings to `/v1/browser` and block the ingester event loop for about 0.6-1.9 s per request (health checks were delayed too). Add a regression test that redacts a 100 KB string of `a` and of `a.` repeated within a small time budget.

Impact to account for:
An unauthenticated remote attacker can stall the whole server process. In the test, one 50 KB request body took about 4.2 s of synchronous CPU, and an unrelated `GET /health2` issued during that window was delayed 4.16 s. Cost grows quadratically: 100 KB takes roughly 12 s and 200 KB roughly 48 s. Concurrent requests give sustained denial of service. The same input also stalls the edge SDK. A 20 KB attacker-controlled error message took 3.9 s in `withAutter`, which can exhaust Worker CPU limits. The precondition is that the application puts an attacker-influenced string into runtimeContext/runtimeLogger/captureException attributes, an error message, or an operation name. This is common (search terms, validation errors that echo input, usernames).

Flagged by Autter security & observability checks.

@autter-dev

autter-dev Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

🧪 Autter test run

Autter checked be20986b.

This PR changes 80 source files. It ran the project's test command for the workspace this PR touched, because those suites are the fastest check that the change did not break existing behavior. It wrote 3 temporary tests for changed files that had no matching test, so the new behavior is still checked. It also executed 3 checks from the PR's test plan.

Targeted agent checks: Autter ran targeted checks against the files this PR changed — it executed uncovered test-plan items and, where a changed file had no matching test, wrote a temporary test to verify the new behavior.

Execution summary: 16 checks executed · 14 passed · 2 failed.

Project test commands

Scope Command Result
@autter/otlp-ingester npm run test ✅ passed · 2s

Autter targeted verification

11 tests executed · 10 passed · 1 failed.

Critical feature verification

Could not run: no valid structured result (exit=0, hadFile=true);

Code-level simulations

Revision be20986b7362 · 400.6s · up to 2 scenarios in parallel

  • Browser coded-error ingestion preserves privacy and grouping — passed (code_diff, code_diff, spec, spec)
    Source: packages/otlp-ingester/src/normalize-browser.ts — ...liftErrorFields(event.context),
    Source: packages/otlp-ingester/src/fingerprint.ts — ? { fingerprint: codeFingerprint(input.service, code), scheme: "code-v1" }
    Source: docs/REQUESTS-AND-ERRORS.md — A code groups every occurrence into one issue (fingerprint scheme code-v1: service + code), across message variants, routes and sources
    Source: PR description — Send a beacon with autter.error.why: "declined for [redacted email]" and confirm the stored and sink-forwarded why has the email masked
    Ran: node '.autter/scratch/browser-coded-error-ingestion.mjs'
    ✅ Both coded events are stored with fingerprint_scheme code-v1 and the same fingerprint despite their different messages and routes.: expected 2 coded occurrence rows with code-v1, one shared fingerprint, and distinct messages and routes; observed {"codedRows":2,"fingerprintSchemes":["code-v1"],"fingerprintCount":1,"messageCount":2,"routeCount":2} (database)
    ✅ The uncoded event is stored with message-v1 and matches the fixed historical message-v1 fingerprint for its input.: expected message-v1 fingerprint b9f7c442d6ab2e3434b714baa4d5f2d4; observed {"rowFound":true,"fingerprintScheme":"message-v1","fingerprint":"b9f7c442d6ab2e3434b714baa4d5f2d4"} (database)
    ✅ The email is masked in the stored error_why, the stored sanitized context, and the sink-forwarded why field.: expected stored error_why, stored sanitized context, and sink why all equal 'declined for [redacted]' without retaining the email; observed {"storedRows":2,"storedWhyMasked":2,"storedContextsMasked":2,"sinkWhyMasked":2,"personalDataRetained":false} (integration)
    ✅ The response is accepted and all expected occurrences appear once in the sink batch.: expected HTTP 202 with accepted=3 and all three expected occurrences present exactly once in the sink batch; observed {"httpStatus":202,"accepted":3,"sinkBatchReceived":true,"sinkOccurrenceCount":3,"expectedOccurrencesFoundOnce":true} (integration)
    Indexed blast radius: 2 direct / 4 transitive dependents (indexed 2026-08-25T18:32:07.053Z).
  • Logger-only log promotion deduplicates across batch and trace path — passed (code_diff, code_diff, spec)
    Source: packages/otlp-ingester/src/log-promotion.ts — if (traces.has(occurrence.traceId)) continue;
    Source: packages/otlp-ingester/src/server.ts — await Promise.all([
    store.insertLogs(ctx, logs),
    store.insertOccurrences(ctx, promoted),
    store.insertMetricPoints(ctx, metricPoints),
    ]);
    Source: PR description — /v1/logs promotes error records from logger-only and edge mode to issues, deduplicated by trace id.
    Ran: node '.autter/scratch/log-promotion-dedupe.mjs'
    ✅ All input log rows persist, while the first request promotes only the first same-trace candidate and the trace-id-free candidate; the previously seen trace is not promoted.: expected runtime_logs contains 7 rows; promoted messages are ["Payment declined","Trace-id-free promoted error"]; the existing trace is skipped; observed {"persistedLogRows":7,"inputLogRows":7,"promotedMessages":["Payment declined","Trace-id-free promoted error"]} (service)
    ✅ The lookup-failure request still promotes its error instead of silently dropping it.: expected ClickHouse and sink both contain occurrence message "Promoted despite lookup failure" after lookup failure; observed {"clickhousePromoted":true,"sinkPromoted":true} (service)
    ✅ Promoted rows and sink occurrences retain the valid error code, request id, route, and correct code-v1 fingerprint; promotion rollups count promoted errors.: expected {"persistedOccurrence":{"errorCode":"billing.declined","requestId":"req_abcdefgh12","route":"/checkout/42","fingerprint":"2fdbbed8f67b813b64ba70154f9a7bb0","fingerprintScheme":"code-v1"},"sinkOccurrence":{"errorCode":"billing.declined","requestId":"req_abcdefgh12","route":"/checkout/42","fingerprint":"2fdbbed8f67b813b64ba70154f9a7bb0","fingerprintScheme":"code-v1"},"promotedErrorCount":3,"promotedRequestCount":3}; observed {"persistedOccurrence":{"errorCode":"billing.declined","requestId":"req_abcdefgh12","route":"/checkout/42","fingerprint":"2fdbbed8f67b813b64ba70154f9a7bb0","fingerprintScheme":"code-v1"},"sinkOccurrence":{"errorCode":"billing.declined","requestId":"req_abcdefgh12","route":"/checkout/42","fingerprint":"2fdbbed8f67b813b64ba70154f9a7bb0","fingerprintScheme":"code-v1"},"promotedErrorCount":3,"promotedRequestCount":3} (integration)
    ✅ The lookup is tenant-scoped, and an unmarked or non-error record does not become an occurrence.: expected all candidate trace lookups use the dedicated tenant; unmarked/non-error messages are not promoted; observed {"lookupTenants":[{"org":"sim-org-6e943aeb-9023-4ebd-ba4d-306f32b42aca","repo":"sim-repo-970bb30c-01b5-49c5-a04c-b03f6939b0e3"},{"org":"sim-org-6e943aeb-9023-4ebd-ba4d-306f32b42aca","repo":"sim-repo-970bb30c-01b5-49c5-a04c-b03f6939b0e3"}],"unmarkedOrNonErrorPromoted":[]} (service)
  • Node request summaries link request IDs, outcomes, and coded responses — execution incomplete (code_diff, code_diff, spec, spec)
    Source: packages/runtime-node/src/requests.ts — const requestId = resolveRequestId(
    options.trustRequestId === false ? undefined : req.headers[header],
    );
    Source: packages/runtime-node/src/requests.ts — emitSummary(state, {
    http: {
    "http.request.method": input.method,
    "http.route": input.route,
    "http.response.status_code": input.status,
    Source: docs/REQUESTS-AND-ERRORS.md — An inbound x-request-id matching ^[\w.-]{8,128}$ is honoured; anything else is replaced with crypto.randomUUID().
    Source: docs/REQUESTS-AND-ERRORS.md — an expected coded error → degraded; a thrown error (one that reached the error middleware or escaped a fetch handler) or status ≥ 500 → failed
    Ran: node '.autter/scratch/request-summaries.mjs'
    Could not complete: Error: Simulation did not execute every planned assertion exactly once; verdict discarded.
  • Edge request export reaches log promotion and the sink — execution incomplete (code_diff, code_diff, spec)
    Source: packages/runtime-edge/src/index.ts — { "autter.capture.mode": "log", ...errorAttributes(err) },
    Source: packages/runtime-edge/src/index.ts — waitUntil.call(
    (ctx as Partial | undefined)?.waitUntil ? ctx : env,
    flush,
    );
    Source: docs/REQUESTS-AND-ERRORS.md — Workers / Vercel Edge / Deno / Bun | withAutter from @autter/runtime-edge
    Ran: node '.autter/scratch/edge-log-promotion.mjs'
    Could not complete: Error: Simulation did not emit exactly one completed assertion report.
  • Sink v1 retains nullable request fields and zero status — failed (code_diff, spec)
    Source: packages/otlp-ingester/src/sink.ts — ...(traceId ? { traceId } : {}),
    ...(route ? { route } : {}),
    ...(method ? { method } : {}),
    ...(statusCode != null ? { statusCode } : {}),
    Source: PR description — Confirm traceId, route, method and statusCode are present (including null and 0) and the new fields appear only when set.
    Ran: node '.autter/scratch/sink-nullable-fields.mjs'
    ❌ The webhook remains version 1 and includes traceId, route, and method keys with null values when those occurrence fields are null.: expected {"version":1,"traceId":null,"route":null,"method":null}; observed {"version":1,"traceId":"","route":"","method":""} (integration)
    ✅ The webhook preserves statusCode 0 rather than dropping it, and preserves populated request fields on the second occurrence.: expected {"nullableStatusCode":0,"populatedRequest":{"traceId":"trace-simulation-001","route":"/api/simulation","method":"POST","statusCode":201}}; observed {"nullableStatusCode":0,"populatedRequest":{"traceId":"trace-simulation-001","route":"/api/simulation","method":"POST","statusCode":201}} (integration)
    ✅ New optional coded-error fields are included when set and omitted when absent.: expected {"absentOccurrence":["errorCode:","why:","fix:","link:","expected:"],"populatedOccurrence":{"errorCode":"SIMULATION_FAILURE","why":"The simulated operation failed.","fix":"Retry the simulated operation.","link":"https://example.invalid/simulation","expected":true}}; observed {"absentOccurrence":{"errorCode":"","why":"","fix":"","link":"","expected":""},"populatedOccurrence":{"errorCode":"SIMULATION_FAILURE","why":"The simulated operation failed.","fix":"Retry the simulated operation.","link":"https://example.invalid/simulation","expected":true}} (integration)
    Root cause (confirmed): Confirmed: sinkOccurrence omits null traceId, route, and method because it conditionally spreads each field only when truthy (lines 437-439). These RuntimeOccurrence fields are explicitly nullable, and the supplied integration expectation requires them to remain present with null values. The adjacent statusCode check uses != null, so it preserves 0 as observed; absent optional coded-error fields are likewise correctly omitted. ClickHouse insertion maps null request values to empty-string/default representations, but that does not cause this webhook result: after the storage promises resolve, the server enqueues the original in-memory fingerprinted occurrences. Note that this requested wire contract conflicts with checked-out docs and tests, which currently say/assert that null request fields are omitted.
    packages/otlp-ingester/src/normalize-otlp.ts:277 — OTLP exception occurrences are produced here; the route and method are derived from span attributes and traceId is explicitly set to span.traceId ?? null (lines 290-294). The occurrence type declares route, method, statusCode, and traceId as nullable in packages/otlp-ingester/src/types.ts:38-46.
    packages/otlp-ingester/src/server.ts:182 — fingerprintAll spreads each normalized input into a RuntimeOccurrence, adding fingerprints/derived fields without changing those request fields (lines 186-195).
    packages/otlp-ingester/src/clickhouse.ts:330 — Persistence writes the occurrence fields to runtime_error_occurrences; null route/method/traceId become empty strings and null statusCode becomes 0 via ?? (lines 340-370). This is a separate representation and does not mutate the input objects.
    packages/otlp-ingester/src/server.ts:323 — The trace handler waits for occurrence/span/metric/LLM inserts, then enqueues the original fingerprinted array (lines 323-335); it does not reload occurrences from ClickHouse before forwarding.
    packages/otlp-ingester/src/sink.ts:201 — buildBatch JSON-serializes occurrences through sinkOccurrence (lines 201-215). In sinkOccurrence, the truthy conditions at lines 437-439 omit null traceId, route, and method. The statusCode condition at line 440 correctly includes zero, while excluding null.
    packages/otlp-ingester/src/sink.ts:349 — send posts the already-serialized batch body to the configured webhook (lines 349-359), which is the integration boundary where the fake receiver observes the omitted keys.
    Suggested fix: To satisfy the supplied contract, serialize traceId, route, method, and statusCode as own properties even when null (for example, include those four fields directly in sinkOccurrence's returned object); this preserves statusCode 0 and emits null for unknown values. Update the conflicting omission contract in docs/ARCHITECTURE.md:351-357 and packages/otlp-ingester/src/coded-errors.test.ts:385-394, 662-665, adding coverage that null values are present. If omission is the intended public contract instead, revise the supplied assertion/spec rather than changing serialization.
    Regression: Identical harness and assertions passed on revision e4e2f00. The causal commit could not be isolated within the bounded linear-history replay.
    Indexed blast radius: 2 direct / 4 transitive dependents (indexed 2026-08-25T18:32:07.053Z).
  • Browser capture survives throwing error metadata getters — passed (code_diff, spec)
    Source: packages/runtime-browser/src/index.ts — for (const key of ["code", "why", "fix", "link", "expected", "requestId"]) {
    try {
    e[key] = (error as Record<string, unknown>)[key];
    } catch {
    /* ignore unreadable metadata */
    }
    }
    Source: PR description — In a browser test page, throw an Error subclass whose code getter throws and confirm captureException still enqueues the original exception.
    Ran: node '.autter/scratch/browser-throwing-error-metadata.mjs'
    ✅ Each original exception is enqueued once with its original message and error type despite metadata getter failures.: expected Exactly one CodedGetterError event with message "code getter original message" and exactly one Error event with message "why getter original message".; observed {"events":[{"message":"code getter original message","errorType":"CodedGetterError","context":{"autter.trail":"nav:/checkout"}},{"message":"why getter original message","errorType":"Error","context":{"autter.trail":"nav:/checkout"}}],"captureErrors":[],"observationError":null} (code)
    ✅ Unreadable metadata is absent from context, and the getter exception does not replace or prevent reporting of the original error.: expected Both original events are reported; autter.error.code and autter.error.why are absent, and neither getter exception appears in the event payload.; observed {"events":[{"message":"code getter original message","errorType":"CodedGetterError","context":{"autter.trail":"nav:/checkout"}},{"message":"why getter original message","errorType":"Error","context":{"autter.trail":"nav:/checkout"}}],"captureErrors":[],"observationError":null} (code)
    Indexed blast radius: 0 direct / 0 transitive dependents (indexed 2026-08-25T18:32:07.053Z).
    Source unavailable: Production route aggregates are unavailable: Runtime queries are not configured. No retained production error patterns were available for this repository.
    Coverage limitation: The supplied production-pattern list is empty, so these scenarios use only changed source and supplied design requirements; no production runtime IDs are available to cite.
    Coverage limitation: The ClickHouse-backed scenarios use disposable HTTP fakes and validate application inserts, lookup behavior, and sink forwarding, but do not validate real ClickHouse migration execution, materialized-view aggregation, TTL alteration, or engine-specific idempotency.
    Coverage limitation: The edge-to-ingester simulation uses local endpoints and cannot verify deployment-specific Workers, Vercel Edge, Deno, or Bun runtime constraints.
    Coverage limitation: The browser capture simulation uses stubbed browser APIs and does not exercise a real cross-origin browser/CORS environment.

Declared tests: 30 test file(s) found — 23 ran, 0 not observed in suite output, 7 did not run.

The project's command reported 10 of them. Autter ran the other 13 files one by one so every declared test has a verdict (13 passed, 0 failed).

"Did not run" means no test command that Autter executed covers the file, so nothing in it was checked.

⚠️ Test cases that did not run
  • ⛔ packages/runtime-browser/test/client-context.test.mjs (1 case) — no test suite covers this file
  • ⛔ packages/runtime-browser/test/csp-actions.test.mjs (1 case) — no test suite covers this file
  • ⛔ packages/runtime-browser/test/network.test.mjs (1 case) — no test suite covers this file
  • ⛔ packages/runtime-node/test/autoflush.test.mjs (7 cases) — no test suite covers this file
  • ⛔ packages/runtime-node/test/lifecycle.test.mjs (4 cases) — no test suite covers this file
  • ⛔ packages/runtime-node/test/logger-ingester.test.mjs (1 case) — no test suite covers this file
  • ⛔ packages/runtime-node/test/relay.test.mjs (2 cases) — no test suite covers this file

Tests for this change

Autter ran the existing tests for 5 changed files, and it wrote 3 temporary tests for changed files that had none (3 passed, 0 failed).

Change coverage (is each changed file's behavior verified by a test?)

Changed file Related test Result
examples/edge-worker/src/index.js — ✅ no declared test — temporary test passed
examples/express-app/server.js — ✅ no declared test — temporary test passed
packages/otlp-ingester/src/context.ts packages/otlp-ingester/src/coded-errors.test.ts ✅ existing test passes
packages/otlp-ingester/src/error-fields.ts packages/otlp-ingester/src/coded-errors.test.ts ✅ existing test passes
packages/otlp-ingester/src/clickhouse.ts packages/otlp-ingester/src/coded-errors.test.ts ✅ existing test passes
packages/otlp-ingester/src/config.ts packages/otlp-ingester/src/continuous-detection.test.ts ✅ no declared test — temporary test passed
packages/otlp-ingester/src/fingerprint.ts packages/otlp-ingester/src/coded-errors.test.ts ✅ existing test passes
packages/otlp-ingester/src/log-promotion.ts packages/otlp-ingester/src/coded-errors.test.ts ✅ existing test passes
🤖 Coverage-check evidence

examples/edge-worker/src/index.js

examples/express-app/server.js

packages/otlp-ingester/src/context.ts

packages/otlp-ingester/src/error-fields.ts

packages/otlp-ingester/src/clickhouse.ts

packages/otlp-ingester/src/config.ts

packages/otlp-ingester/src/fingerprint.ts

packages/otlp-ingester/src/log-promotion.ts

Temporary tests are written under .autter/scratch/ for verification only — they are never committed to the repository.

Test plan (from the PR description)

  • ✅ Run npm ci && npm run build, then npm test, and confirm the runtime-next dts build resolves @autter/runtime-edge (exercises scripts/ensure-built.mjs). — verified by agent execution
  • ✅ Run npm run size -w @autter/runtime-browser and confirm the bundle is within the 5 KB brotlied budget. — verified by agent execution
  • ⬜ Start local ClickHouse and the ingester (Docker Compose, ports 8123 and 4318). Confirm migrations apply twice without error and that runtime_occurrences has the new code columns and request-id index, and runtime_logs has its TTL. — Run is unit tests, not full Docker Compose setup.
  • ⬜ POST a /v1/browser beacon with autter.error.code: "payments.card_declined", autter.error.why/fix/link and autter.request.id. Read back from ClickHouse and confirm fingerprintScheme = "code-v1" and the lifted fields. — Run does not cover POST /v1/browser beacon E2E.
  • ⬜ Repeat the beacon with a different message and route but the same code, and confirm the same fingerprint. — Run does not cover repeating beacons to confirm fingerprint.
  • ⬜ Send two beacons with id-shaped codes (req_8f3a1c4d, req_9b2e77aa) and identical stacks. Confirm they do not create separate code-v1 issues. — Run does not cover id-shaped code beacon logic.
  • ⬜ Send a beacon with autter.error.why: "declined for jane@acme.com" and confirm the stored and sink-forwarded why has the email masked, consistent with attributes.context. — Run does not cover specific email masking test.
  • ⬜ Send an uncoded error and confirm its fingerprint equals the one produced on main (message-v1, unchanged). — Run does not cover uncoded error fingerprint comparison.
  • ❌ Configure AUTTER_SINK_URL to a local receiver and inspect the webhook body. Confirm traceId, route, method and statusCode are present (including null and 0) and the new fields appear only when set. — agent execution observed the wrong behavior
  • ⬜ Run examples/express-app and hit /boom, /api/ok and POST /api/checkout. Confirm request summaries are emitted and linked to occurrences via autter.request.id. — Run does not cover examples/express-app tests.
  • ⬜ Run examples/edge-worker with wrangler dev and confirm a request summary and a coded error reach the ingester. — Run does not cover examples/edge-worker tests.
  • ⬜ In a browser test page, throw an Error subclass whose code getter throws and confirm captureException still enqueues the original exception. — Run does not cover browser test page steps.
🤖 Agent-executed checks

✅ Run npm ci && npm run build, then npm test, and confirm the runtime-next dts build resolves @autter/runtime-edge (exercises scripts/ensure-built.mjs).
npm ci succeeded. All workspace builds passed, including runtime-next DTS declarations for @autter/runtime-edge. npm test passed: runtime-core 15, runtime-node 55 (1 skipped), runtime-edge 7, runtime…

✅ Run npm run size -w @autter/runtime-browser and confirm the bundle is within the 5 KB brotlied budget.
size-limit: 3.92 kB minified and brotlied; configured limit 5 kB.

❌ Configure AUTTER_SINK_URL to a local receiver and inspect the webhook body. Confirm traceId, route, method and statusCode are present (including null and 0) and the new fields appear only when set.
The receiver got statusCode:0 and set errorCode/why/requestId fields, but null traceId, route, and method were omitted (observed as undefined). The assertion requiring those keys with null values fai…

⬜ items could not be verified automatically and still need a manual check.

@autter-dev

autter-dev Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

packages/runtime-node/test/logger.test.mjs:1 · test_failure

🟡 [deterministic] Tests failed: packages/runtime-node/test/logger.test.mjs — Risk: 40/100

packages/runtime-node/test/logger.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/logger.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/logger.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js'
}

Node.js v24.21.0
✖ test/logger.test.mjs (70.969073ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fai…
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-node/test/logger.test.mjs fails when run with `node --test 'test/logger.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/logger.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/es. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@autter-dev

autter-dev Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

packages/runtime-node/test/redact.test.mjs:1 · test_failure

🟡 [deterministic] Tests failed: packages/runtime-node/test/redact.test.mjs — Risk: 40/100

packages/runtime-node/test/redact.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/redact.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/redact.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js'
}

Node.js v24.21.0
✖ test/redact.test.mjs (47.900683ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fai…
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-node/test/redact.test.mjs fails when run with `node --test 'test/redact.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/redact.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/es. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 10 finding(s) as review threads below (🟡 10). Each carries a copy-paste AI fix prompt.

@@ -0,0 +1,627 @@
import assert from "node:assert/strict";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Tests failed: @autter/otlp-ingester — Risk: 40/100

npm run test could not verify the @autter/otlp-ingester test suite in packages/otlp-ingester — this is not evidence that this PR breaks it.

What happened: The test suite failed because the required packages @clickhouse/client and zod are not found during import in the otlp-ingester package.

How to fix: This looks like an incomplete install or missing lockfile issue. Please run npm install to ensure all dependencies are properly installed before running tests again.

Test output
ckageName, fileURLToPath(base), null);
        ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'zod' imported from /tmp/autter-agentic-PpMD8i/packages/otlp-ingester/src/normalize-browser.ts
    at Object.getPackageJSONURL (node:internal/modules/package_json_reader:331:9)
    at packageResolve (node:internal/modules/esm/resolve:784:25)
    at moduleResolve (node:internal/modules/esm/resolve:873:18)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at nextStep (node:internal/modules/customization_hooks:189:26)
    at resolveBaseSync (file:///tmp/autter-agentic-PpMD8i/node_modules/tsx/dist/register-zZ7SWseA.mjs:2:9102)
    at resolveDirectorySync (file:///tmp/autter-agentic-PpMD8i/node_modules/tsx/dist/register-zZ7SWseA.mjs:2:10315)
    at resolveTsPathsSync (file:///tmp/autter-agentic-PpMD8i/node_modules/tsx/dist/register-zZ7SWseA.mjs:2:11547) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v24.21.0
✖ src/continuous-detection.test.ts (391.735303ms)
✔ occurrenceIdFor is a pure function of the signal (2.548885m…
🛠 AI fix prompt (copy & paste into your coding agent)
The @autter/otlp-ingester test suite (`npm run test` in `packages/otlp-ingester`) fails. The test suite failed because the required packages @clickhouse/client and zod are not found during import in the otlp-ingester package. Fix: This looks like an incomplete install or missing lockfile issue. Please run npm install to ensure all dependencies are properly installed before running tests again.

Flagged by Autter security & observability checks.

@@ -0,0 +1,233 @@
import assert from "node:assert/strict";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Tests failed: packages/runtime-core/test/core.test.mjs — Risk: 40/100

packages/runtime-core/test/core.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/core.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/packages/runtime-core/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-core/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-core/test/core.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/packages/runtime-core/dist/index.js'
}

Node.js v24.21.0
✖ test/core.test.mjs (50.678393ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
…
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-core/test/core.test.mjs fails when run with `node --test 'test/core.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-core/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-core/test/core.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@@ -0,0 +1,143 @@
import assert from "node:assert/strict";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Tests failed: packages/runtime-edge/test/edge.test.mjs — Risk: 40/100

packages/runtime-edge/test/edge.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/edge.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/packages/runtime-edge/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-edge/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-edge/test/edge.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/packages/runtime-edge/dist/index.js'
}

Node.js v24.21.0
✖ test/edge.test.mjs (51.798182ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
…
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-edge/test/edge.test.mjs fails when run with `node --test 'test/edge.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-edge/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-edge/test/edge.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@@ -0,0 +1,65 @@
import assert from "node:assert/strict";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Tests failed: packages/runtime-next/test/next.test.mjs — Risk: 40/100

packages/runtime-next/test/next.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/next.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/node_modules/@autter/runtime-node/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the dependency build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/node_modules/@autter/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-next/dist/chunk-LSSGZZDH.js
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/node_modules/@autter/runtime-node/dist/index.js'
}

Node.js v24.21.0
✖ test/next.test.mjs (47.514914ms)
ℹ tests 1
ℹ…
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-next/test/next.test.mjs fails when run with `node --test 'test/next.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/node_modules/@autter/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-next/dist/chunk-LSSGZZDH.js
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:inter. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@@ -0,0 +1,34 @@
import assert from "node:assert/strict";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Tests failed: packages/runtime-node/test/golden.test.mjs — Risk: 40/100

packages/runtime-node/test/golden.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/golden.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/golden.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js'
}

Node.js v24.21.0
✖ test/golden.test.mjs (47.020098ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fai…
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-node/test/golden.test.mjs fails when run with `node --test 'test/golden.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/golden.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/es. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@@ -0,0 +1,53 @@
import assert from "node:assert/strict";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Tests failed: packages/runtime-node/test/hook-mode.test.mjs — Risk: 40/100

packages/runtime-node/test/hook-mode.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/hook-mode.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/hook-mode.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js'
}

Node.js v24.21.0
✖ test/hook-mode.test.mjs (77.730288ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0…
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-node/test/hook-mode.test.mjs fails when run with `node --test 'test/hook-mode.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/hook-mode.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@@ -0,0 +1,68 @@
import assert from "node:assert/strict";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Tests failed: packages/runtime-node/test/logging-only.test.mjs — Risk: 40/100

packages/runtime-node/test/logging-only.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/logging-only.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/logging-only.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js'
}

Node.js v24.21.0
✖ test/logging-only.test.mjs (70.975485ms)
ℹ tests 1
ℹ suites 0
ℹ …
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-node/test/logging-only.test.mjs fails when run with `node --test 'test/logging-only.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/logging-only.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modu. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@@ -0,0 +1,217 @@
import assert from "node:assert/strict";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Tests failed: packages/runtime-node/test/operations.test.mjs — Risk: 40/100

packages/runtime-node/test/operations.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/operations.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/operations.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js'
}

Node.js v24.21.0
✖ test/operations.test.mjs (72.880891ms)
ℹ tests 1
ℹ suites 0
ℹ pass…
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-node/test/operations.test.mjs fails when run with `node --test 'test/operations.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/operations.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/module. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@@ -0,0 +1,308 @@
import assert from "node:assert/strict";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Tests failed: packages/runtime-node/test/requests.test.mjs — Risk: 40/100

packages/runtime-node/test/requests.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/requests.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/requests.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js'
}

Node.js v24.21.0
✖ test/requests.test.mjs (78.706788ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ…
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-node/test/requests.test.mjs fails when run with `node --test 'test/requests.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/requests.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@@ -0,0 +1,89 @@
import assert from "node:assert/strict";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] Tests failed: packages/runtime-node/test/sinks.test.mjs — Risk: 40/100

packages/runtime-node/test/sinks.test.mjs fails. The project's own test command never reported this file, so Autter ran it on its own with node --test 'test/sinks.test.mjs'.

Failing tests

  • the file failed before any test reported — The test expects /tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js to exist, but Node.js reports it cannot find the module. The observed value is an ERR_MODULE_NOT_FOUND error confirming the build artifact is missing.
node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/sinks.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:705:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:725:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:763:56)
    at #resolve (node:internal/modules/esm/loader:687:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:607:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:276:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:381:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js'
}

Node.js v24.21.0
✖ test/sinks.test.mjs (49.09521ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1…
🛠 AI fix prompt (copy & paste into your coding agent)
The test file packages/runtime-node/test/sinks.test.mjs fails when run with `node --test 'test/sinks.test.mjs'`: node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/autter-agentic-PpMD8i/packages/runtime-node/dist/index.js' imported from /tmp/autter-agentic-PpMD8i/packages/runtime-node/test/sinks.test.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm. Fix the code (or the test if it is genuinely stale) so it passes.

Flagged by Autter security & observability checks.

@autter-dev

autter-dev Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Autter product walk

Autter did not drive this change in a browser.

This PR changes a backend/API, but Autter could not find a start, dev, or run command for it, so it could not boot the service and exercise its endpoints. Autter ran targeted checks only. Add a start command (a start/dev script, or make it discoverable on the repo's "How to run this" tab), and Autter exercises the endpoints on the next push.

@autter-dev

autter-dev Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

.:1 · product_walk_failure

🟡 [deterministic] Product walk failed: npm test — Risk: 40/100

Planned command npm test did not produce a usable result; this is not evidence that this PR breaks the product behavior.

The PR touches OTLP ingestion (sink.ts/server.ts), core fingerprinting/redaction/error-code logic, and versioned golden fixtures — all library packages with no UI/server start. There is no start command, so HTTP steps are impossible. The right adversarial exercise is the targeted package test suites: OTLP ingester tests stress duplicate/concurrent batch delivery and the sink's idempotency/partial-

Expected: All package suites pass: runtime-core exercises fingerprint-code boundary inputs (empty/unicode/oversized) and redact rules; runtime-node replays the golden-1.4.0-logs fixture proving schema_version_s

Output: https://autter-app-storage.s3.ap-south-1.amazonaws.com/pr-review/Autter-dev/autter-runtime/25/8ffda4f0b739b8c96d0e5cdef35348463f12ec7d/walk/fn-step-1.txt

1 attempt(s), 0 of 3 retries used.
Attempt 1: The runtime-next declaration build cannot resolve the sibling @autter/runtime-edge and @autter/runtime-browser packages. This is consistent with missing workspace build artifacts; run the allowed build preparation before retrying the unchanged test command. The build fails during declaration generation because @autter/runtime-next cannot resolve @autter/runtime-browser or @autter/runtime-edge. The allowed npm run build preparation also failed with the same missing-module errors, so it cannot be repeated and no permitted correction is available. Preparation npm run build: failed.
AI found no recoverable setup correction

src/edge.ts(14,15): error TS2307: Cannot find module '@autter/runtime-edge' or its corresponding type declarations.

Error: error occurred in dts build
at Worker. (/tmp/autter-agentic-PpMD8i/node_modules/tsup/dist/index.js:1545:26)
at Worker.emit (node:events:514:28)
at MessagePort. (node:internal/worker:350:14)
at [nodejs.internal.kHybridDispatch] (node:internal/event_target:848:20)
at MessagePort. (node:internal/per_context/messageport:23:28)
DTS Build error
src/client.ts(23,8): error TS2307: Cannot find module '@autter/runtime-browser' or its corresponding type declarations.

npm error Lifecycle script build failed with error:
npm error code 1
npm error path /tmp/autter-agentic-PpMD8i/packages/runtime-next
npm error workspace @autte

🛠 AI fix prompt (copy & paste into your coding agent)
src/edge.ts(14,15): error TS2307: Cannot find module '@autter/runtime-edge' or its corresponding type declarations.

Error: error occurred in dts build
    at Worker.<anonymous> (/tmp/autter-agentic-PpMD8i/node_modules/tsup/dist/index.js:1545:26)
    at Worker.emit (node:events:514:28)
    at MessagePort.<anonymous> (node:internal/worker:350:14)
    at [nodejs.internal.kHybridDispatch] (node:inter

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter blocked this PR: build; test. See the findings below and the full PR review for details.

@autter-dev

autter-dev Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Autter review completed — merge blocked

Autter completed its review and found at least one confirmed merge-blocking finding.

Autter blocked this PR: test; test_failure; 1 confirmed correctness/runtime finding(s). See the findings below and the full PR review for details.

Other findings explain the additional warnings, but they did not independently block this PR.

The merge gate remains blocked until the confirmed finding is resolved.

Retry

Retrying reruns the review on the same commit. If the commit is unchanged, the same finding and block may be reproduced.

  • Click Retry review on the autter/review-gate check below, or
  • Comment @autter review on this PR

Autter will post a new comment when the retry starts.

- Redaction regexes are linear on adversarial strings: email, URL
  credential, JWT, Slack and Autter-key patterns only start a match at the
  beginning of a run, query stripping is procedural, and strings are capped
  before scrubbing (runtime-core, ingester, browser).
- runtime-edge queues each record with its own destination and drops
  records when no key is configured.
- /v1/logs promotion lookups are bounded in span, count and time range,
  newest first, with a per-tenant PROMOTION_LOOKUPS_PER_MINUTE budget.
- runtime-browser reads optional error metadata defensively.
- The sink keeps statusCode 0; proxy-addr 2.0.8 (CVE-2026-90711).
- Test scripts build the workspace packages they import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sagnik11

sagnik11 commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

Pushed 4489bcd addressing the review:

  • ReDoS in redaction (runtime-core/src/redact.ts): fixed. Same pattern also fixed in the ingester's sanitizeRuntimeContext (it scrubbed before truncating) and the browser. Covered by timing tests on 256 KB–1 MB adversarial inputs.
  • Edge queue used another request's credentials: fixed. Records carry their own destination, and nothing is queued without a key. The regression test fails on the previous code.
  • Unbounded promotion lookup: fixed. Windows are ≤10 min, ≤4 per request, newest first, last 24 h only, with a per-tenant PROMOTION_LOOKUPS_PER_MINUTE budget.
  • Throwing metadata getters in the browser: fixed.
  • statusCode 0 dropped: fixed. In practice the browser path always sends null.
  • proxy-addr CVE-2026-90711: bumped to 2.0.8. It was already on main.
  • Build/test failures: the suites in the review sandbox ran before the workspace packages they import were built. Test scripts now build those dependencies first, and runtime-next/runtime-node tests pass from empty dist/ folders. The ingester failures (zod, @clickhouse/client not found) were missing installs in the sandbox.

Not changed (false positives):

  • runtime-edge/src/index.ts:265 sensitive data: the error goes through makeRedactor, and the client body uses toClientError.
  • requests.test.mjs:48: an intentional redaction test.
  • PHONE_NUMBER: a date and a test id.
  • Hallucinated import: a local module.
  • [^]: valid JS for "any char".
  • Batch/pagination: DDL, not loops.

@autter review

@autter-dev

autter-dev Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

🔄 Re-running my review on the latest commit — fresh findings will appear shortly.

🤖 Replying to your mention — mention @autter again to continue.

@autter-dev

autter-dev Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

🔄 Retrying the PR review via @autter review. Fresh findings will appear on this PR shortly.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 1 finding(s) as review threads below (🟠 1). Each carries a copy-paste AI fix prompt.

// Behaviour is unchanged for real secrets.
const out = redact({
mail: "contact jane.doe+x@example.co.uk now",
db: "postgres://user:pw@host/db",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Database connection URL with embedded credentials — Risk: 69/100

A Postgres connection URL with credentials appears to be hard-coded in source. Treat the credential as compromised and rotate it now. Blast radius — if this credential is exploited it cascades to the downstream usage that depends on this file: endpoints POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom; dependent files packages/otlp-ingester/src/fingerprint.ts, packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts.

References:

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • API endpoints: POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom
  • Dependent files: packages/otlp-ingester/src/fingerprint.ts, packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts
🛠 AI fix prompt (copy & paste into your coding agent)
In `packages/runtime-core/test/core.test.mjs` around line 255 a Postgres connection URL with credentials is hard-coded in source. Treat the literal as compromised even if the PR is closed without merging. Replace the embedded password with an env-var reference (`postgres://app:${PG_PASSWORD}@host/db`) and rotate the database user password on the database server. Move the value to your project's secret manager (AWS Secrets Manager / GCP Secret Manager / Azure Key Vault / HashiCorp Vault / Doppler / 1Password) and load it at runtime via an env var. Diff-only: rotation + amending this PR (drop the literal) is sufficient. Affected services / downstream impact Blast radius — if this credential is exploited it cascades to the downstream usage that depends on this file: endpoints `POST /checkout`, `GET /healthz`, `GET /api/users/:id`, `POST /api/checkout`, `POST /api/users/:id`, `GET /boom`; dependent files `packages/otlp-ingester/src/fingerprint.ts`, `packages/otlp-ingester/src/normalize-otlp.ts`, `packages/otlp-ingester/src/sink.ts`, `packages/otlp-ingester/src/server.sink.test.ts`, `packages/otlp-ingester/src/sink.test.ts`, `packages/otlp-ingester/src/llm.ts`, `packages/otlp-ingester/src/index.ts`, `packages/otlp-ingester/src/auth.ts`..

Flagged by Autter security & observability checks.

@autter-dev

autter-dev Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Release Notes Curator

Impact: release: minor — the diff ships new user-facing capability (request summaries, runtimeContext, coded errors, new @autter/runtime-edge package, /v1/logs request columns) and the docs/CHANGELOG explicitly describe it as additive, with no breaking change to existing 1.4.0 behaviour.

Changelog entry (ready to paste):

## [1.5.0] - 2026-10-08

Packages: `otlp-ingester` 1.5.0, `runtime-node` 1.5.0, `runtime-next` 1.5.0,
`runtime-browser` 1.4.0, new `runtime-edge` 1.0.0.
Upgrade the ingester **first**; everything below is additive — 1.4.0 code keeps
working unchanged.

### Features

- **Request summaries** — one record per HTTP request (route template, status,
  duration, outcome, request id, context, inline messages), always kept, with
  `x-request-id` honoured, echoed and exposed to CORS. Express/Connect
  (`autterRequests`), Fastify (`autterFastify`), fetch-style handlers and Next.js
  route handlers (`withRuntimeRequest`).
- **`runtimeContext`** — read and set context, outcomes, ids and inline
  debug/info messages from anywhere inside a request or operation; debug/info
  messages fold into the summary instead of being dropped.
- **Coded errors** — `defineRuntimeErrors` / `RuntimeError` give errors a stable
  `code`, plus optional `why`, `fix`, docs link and `expected` flag.
  `captureException` reads them from any error object, and errors sharing a code
  are grouped into a single issue regardless of message or stack. `expected`
  failures (declines, validation, quota) are recorded but never open incidents.
- **Client-safe error responses** — `autterErrorResponse` / `toClientError`
  return `{ error: { message, code, fix, requestId } }` without leaking
  internals.
- **Beyond the request** — `fork`, `runInBackground`, cross-process carriers with
  OpenTelemetry span links, `waitUntil`, and per-operation AI usage rollup.
- **Logger-only mode** — `initAutterLogging` adds requests, operations and coded
  errors to apps that already run their own OpenTelemetry SDK, without a second
  NodeSDK.
- **New `@autter/runtime-edge` 1.0.0** — request summaries, request ids and coded
  errors for Cloudflare Workers, Vercel Edge, Deno and Bun with zero
  dependencies; also available as `@autter/runtime-next/edge`.
- **Developer ergonomics** — enrichers, `otlp`/`console`/`file` sinks (records
  written to `.autter/runtime/*.jsonl` in development) and a
  `@autter/runtime-node/testing` subpath.
- **Self-hosted ingester** — stores the new request and error fields, feeds a new
  per-route request rollup, applies a configurable `LOG_TTL_DAYS` log retention
  (default 14 days) and promotes log-captured exceptions to issues without
  double-counting traced ones.

(Heads-up: the PR already edits CHANGELOG.md with a 1.5.0 section — merge the above into it rather than adding a second entry.)

Custom agent · runs after review · configured in Autter

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 4 finding(s) as review threads below (🟠 4). Each carries a copy-paste AI fix prompt.

const lookups: PromotionLookup[] = [];
let start = 0;
while (start < eligible.length && lookups.length < MAX_LOOKUP_WINDOWS) {
const newest = eligible[start]!.at;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/style/noNonNullAssertion — Risk: 55/100

Forbidden non-null assertion.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/style/noNonNullAssertion` issue at packages/otlp-ingester/src/log-promotion.ts:88: Forbidden non-null assertion.

Flagged by Autter security & observability checks.

if (!exporter.queue.length) return;
exporter.flushing = (async () => {
while (exporter.queue.length) {
const destination = exporter.queue[0]!.destination;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/style/noNonNullAssertion — Risk: 55/100

Forbidden non-null assertion.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/style/noNonNullAssertion` issue at packages/runtime-edge/src/index.ts:243: Forbidden non-null assertion.

Flagged by Autter security & observability checks.

const bodies = [];
const collector = createServer((req, res) => {
let body = "";
req.on("data", (chunk) => (body += chunk));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/suspicious/noAssignInExpressions — Risk: 55/100

The assignment should not be in an expression.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/suspicious/noAssignInExpressions` issue at packages/runtime-node/test/fixtures/golden-scenario.mjs:23: The assignment should not be in an expression.

Flagged by Autter security & observability checks.

const requests = [];
const collector = createServer((req, res) => {
let body = "";
req.on("data", (chunk) => (body += chunk));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [deterministic] Biome: lint/suspicious/noAssignInExpressions — Risk: 55/100

The assignment should not be in an expression.

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the Biome `lint/suspicious/noAssignInExpressions` issue at packages/runtime-node/test/logging-only.test.mjs:17: The assignment should not be in an expression.

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 2 finding(s) as review threads below (🟡 2). Each carries a copy-paste AI fix prompt.

promotionLookups,
} from "./log-promotion.js";
import { sanitizeRuntimeContext } from "./context.js";
import { sinkOccurrence } from "./sink.js";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] oxlint: oxlint — Risk: 45/100

packages/otlp-ingester/src/coded-errors.test.ts:26:10: Identifier sinkOccurrence has already been declared

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the oxlint `oxlint` issue at packages/otlp-ingester/src/coded-errors.test.ts:26: packages/otlp-ingester/src/coded-errors.test.ts:26:10: Identifier `sinkOccurrence` has already been declared

Flagged by Autter security & observability checks.

.replace(/(?<![A-Z0-9._%+-])[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[redacted]")
.replace(
/\b(?:bearer\s+[A-Za-z0-9._~+\/=~-]{10,}|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+|gh[pousr]_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|autter_(?:rt|pat)_[A-Za-z0-9_-]{10,}|(?:AKIA|ASIA)[A-Z0-9]{16})\b/gi,
/\b(?:bearer\s+[A-Za-z0-9._~+\/=~-]{10,}|(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+|gh[pousr]_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9_-]{20,}|(?<![A-Za-z0-9-])xox[baprs]-[A-Za-z0-9-]{10,}|(?<![A-Za-z0-9_-])autter_(?:rt|pat)_[A-Za-z0-9_-]{10,}|(?:AKIA|ASIA)[A-Z0-9]{16})\b/gi,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [deterministic] oxlint: eslint(no-useless-escape) — Risk: 30/100

packages/otlp-ingester/src/context.ts:11:34: Unnecessary escape character '/'

🛠 AI fix prompt (copy & paste into your coding agent)
Fix the oxlint `eslint(no-useless-escape)` issue at packages/otlp-ingester/src/context.ts:11: packages/otlp-ingester/src/context.ts:11:34: Unnecessary escape character '/'

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter could not complete PR review for #25. Autter hit an internal error and could not complete this review. Push a new revision to re-run it.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter blocked this PR: build; test. See the findings below and the full PR review for details.

Sagnik Ghosh and others added 4 commits October 9, 2026 02:16
runtime-node and runtime-edge bundle the private runtime-core package, and
runtime-next imports runtime-node, runtime-edge and runtime-browser for its
type declarations. Building any of them alone before its siblings had been
built failed with "Could not resolve @autter/runtime-core" or TS2307, which
is what the review gate's per-package build hit.

scripts/ensure-built.mjs builds a sibling only when its dist/ is missing, so
the root build (already in dependency order) does no extra work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n hostile attributes

The per-isolate queue dropped whichever record arrived last once it held
maxQueue records or 1 MB, and the summary is always emitted last, so a
request logging 250 warnings exported no summary at all. A summary now
evicts the oldest plain record, and warn/error records are capped at 50 per
request.

rt.error and rt.captureException spread the caller's attributes before
redaction, so a throwing getter or a Proxy made the user's handler throw.
They now copy fields one by one and skip ones that throw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CHANGELOG says cache_creation_tokens counts are no longer masked, but only
the ingester was updated; the SDK redactor still turned them (and
cache_read_input_tokens, cache_creation_input_tokens, cached_tokens and
reasoning_tokens) into "[redacted]". Numeric counts under these keys are
kept now; non-numeric values are still masked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
liftErrorFields took the first non-empty autter.error.code across the
exception event and its span and validated it afterwards, so an invalid code
on the event (ECONNRESET) hid a valid one on the span and the error fell
back to message grouping. Same for autter.request.id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@autter-dev

autter-dev Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

🤖 AI authorship

54% of changed code was AI-written — 0% human, 46% untracked, across 10,010 attributed line(s).

By model: claude:claude-opus-5-5 54%

Most AI-authored files:

  • packages/runtime-node/src/logger.ts — 100% AI (632 lines)
  • packages/otlp-ingester/src/coded-errors.test.ts — 91.7% AI (627 lines)
  • packages/runtime-node/src/requests.ts — 100% AI (613 lines)
  • packages/runtime-node/src/sinks.ts — 100% AI (529 lines)
  • packages/runtime-edge/src/index.ts — 84.7% AI (477 lines)

Partial coverage: some commits in this PR were not authored through the autter CLI.

Provenance measured at authorship time by the autter CLI.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 4 finding(s) as review threads below (🟠 3 · 🟡 1). Each carries a copy-paste AI fix prompt.

} {
const code = validErrorCode(input.errorCode);
return code
? { fingerprint: codeFingerprint(input.service, code), scheme: "code-v1" }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [ai] Declaring an error code moves occurrences to a new code-v1 issue; the 'never regroup' comment is inaccurate — Risk: 50/100

For errors without a valid code, message-v1 hashing is unchanged. But once an occurrence carries a valid autter.error.code, occurrenceFingerprint returns a code-v1 fingerprint built from service + code only. New occurrences of that error then group into a different issue, and the prior message-v1 issue stops receiving them.

The doc comment's claim that existing issues never regroup is inaccurate. It should state that adopting a code re-groups the error. Since the repo treats fingerprinting changes as breaking, this behaviour should be documented and discussed explicitly.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Functions/symbols: normalizeRoute, fingerprintOccurrence, deriveFields, occurrenceIdFor, normalizeTraces, normalizeMetrics, instrumentLlmClient, loadConfig
  • API endpoints: POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom
  • Dependent files: packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts, packages/otlp-ingester/src/normalize-browser.test.ts
  • Scopes: @autter/otlp-ingester
🛠 AI fix prompt (copy & paste into your coding agent)
Treat the code-v1 transition as a breaking fingerprinting change: confirm it was discussed and approved before shipping, document that coded errors regroup into new issues, and correct the claim that existing issues never regroup. If that change is not approved for this release, preserve message-v1 identity.

Flagged by Autter security & observability checks.

return {
...rest,
occurredAt: o.occurredAt.toISOString(),
...(traceId ? { traceId } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [ai] Version 1 sink payload omits previously-null request-context fields — Risk: 50/100

sinkOccurrence no longer emits traceId, route, method or statusCode when they are unknown. Before this change they were always present as null, via the ...o spread. Consumers that test o.route === null or rely on these keys being present will behave differently, yet the payload version stays 1. That conflicts with the rule that wire formats are additive-only. Keep emitting these four fields as null when unknown, and conditionally omit only the genuinely new 1.5.0 fields. Also fix the doc comment, which wrongly lists these four as fields 1.5.0 adds.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Functions/symbols: SinkForwarder.pump, SinkForwarder.enforceBounds, SinkForwarder.scheduleWake, SinkForwarder.untrack, SinkForwarder.maxBufferedBytes, SinkForwarder.send, normalizeTraces, normalizeMetrics
  • API endpoints: POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom
  • Dependent files: packages/otlp-ingester/src/fingerprint.ts, packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts, packages/otlp-ingester/src/normalize-browser.test.ts
  • Scopes: @autter/otlp-ingester
🛠 AI fix prompt (copy & paste into your coding agent)
Preserve the version 1 sink shape for `traceId`, `route`, `method`, and `statusCode`: always emit these keys and use `null` when unknown. Keep only newly introduced fields optional, correct the serializer documentation, and add a compatibility test for absent request context.

Flagged by Autter security & observability checks.

* ids, free text) is not a code and the error groups by message as before.
* Shared spec: runtime-core and the Autter backend validate identically.
*/
export const CODE_PATTERN = /^[a-z][a-z0-9_]*(\.[a-z0-9_]+){0,3}$/;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [ai] CODE_PATTERN accepts id-shaped values, so per-request ids in autter.error.code fragment issues — Risk: 45/100

validErrorCode accepts any lowercase identifier matching ^[a-z][a-z0-9_]*(\.[a-z0-9_]+){0,3}$. That includes id-shaped values like req_8f3a1c4d, despite the doc comment saying ids are not codes.

Because codeFingerprint hashes the raw code with no volatile-token normalisation, two problems follow. A producer that puts a request or entity id in the code slot gets one code-v1 issue per id instead of grouping by message. A beacon sender can likewise mint many distinct issues.

Consider rejecting id-like segments (for example, ones containing digit runs or hex-like tokens) or requiring a namespace dot. At minimum, correct the comment so it does not claim ids are excluded.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Functions/symbols: normalizeRoute, fingerprintOccurrence, deriveFields, occurrenceIdFor, normalizeTraces, normalizeMetrics, instrumentLlmClient, loadConfig
  • API endpoints: POST /checkout, GET /healthz, GET /api/users/:id, POST /api/checkout, POST /api/users/:id, GET /boom
  • Dependent files: packages/otlp-ingester/src/normalize-otlp.ts, packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/server.sink.test.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/llm.ts, packages/otlp-ingester/src/index.ts, packages/otlp-ingester/src/auth.ts, packages/otlp-ingester/src/normalize-browser.test.ts
  • Scopes: @autter/otlp-ingester
🛠 AI fix prompt (copy & paste into your coding agent)
Tighten error-code validation so identifier-like and otherwise high-cardinality values are rejected, while preserving documented stable namespaced codes. Add tests for changing request-ID-shaped codes on otherwise identical errors and verify they fall back to message-v1 fingerprinting.

Flagged by Autter security & observability checks.

@autter-dev

autter-dev Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

🤖 Release Notes Curator

Impact: minor. The release adds new user-facing capabilities, including a new @autter/runtime-edge package, request summaries and coded errors. It stays backward compatible: 1.4.0 code keeps working and the sink payload stays at version: 1.

Changelog entry

Request summaries, coded errors and edge runtime support (Runtime 1.5.0)

  • One summary per request. Express, Fastify, Next.js route handlers and fetch-style handlers now emit a single always-kept record per request. It includes the route, status, duration, outcome and request ID. The x-request-id header is honoured or generated, and it is echoed back to clients. Health and metrics routes can be skipped with ignore.
  • Coded errors, one issue per code. Errors that carry a code such as billing.declined now group into a single issue, whatever the message or stack. You can declare why, fix and link guidance on an error. Expected failures such as declines or validation errors are recorded but never open incidents. Errors without a code group as before.
  • Request context and background work. Attach context to the current request from anywhere in its call tree. Debug and info messages fold into the request summary. Context, request IDs and AI usage and cost totals carry over to background jobs and queues.
  • Edge runtimes. The new @autter/runtime-edge package supports Cloudflare Workers, Vercel Edge, Deno and Bun. It has zero dependencies. Next.js middleware can use @autter/runtime-next/edge.
  • Works with your own OpenTelemetry setup. initAutterLogging adds requests, operations and coded errors without starting a second OpenTelemetry SDK.
  • Local development. New enrichers, console and file sinks, and a testing helper make it easier to inspect records locally.
  • Upgrade note. Upgrade the self-hosted ingester to 1.5.0 before the SDKs. It adds new log and error columns, plus a new LOG_TTL_DAYS setting (default 14).

Custom agent · runs after review · configured in Autter

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 4 finding(s) as review threads below (🟠 3 · 🟡 1). Each carries a copy-paste AI fix prompt.

const startedAt = Date.now();
const requestId = resolveRequestId(request.headers.get(header));
const method = String(request.method || "GET").toUpperCase();
const route = (opts.routeOf?.(request) ?? normalizeRoutePath(pathname)).slice(0, 200);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [ai] Request summaries export opaque path tokens to telemetry — Risk: 65/100

The edge wrapper derives the default route from the untrusted request pathname and only normalizes numeric, UUID, and long-hex segments; arbitrary opaque segments remain intact. Such segments commonly contain password-reset or magic-link tokens (for example, /reset/AbC_def-123), and the summary stores this value as http.route and queues it for OTLP export. The configured redactor does not generally recognize opaque path tokens, so a request carrying a bearer-like secret in its path can disclose that secret to the telemetry endpoint (and to enabled console logging). Prefer framework route templates and, when unavailable, redact or collapse unknown path segments before recording them.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: packages/runtime-edge/src/index.ts, packages/runtime-core/src/request.ts
🛠 AI fix prompt (copy & paste into your coding agent)
Do not export raw request path segments as `http.route`. Use a framework-provided route template where available; otherwise conservatively replace opaque/non-static segments (including mixed alphanumeric, base64-like, and slug tokens) with `:id` before creating summaries. Add a regression test proving a secret-like path token is absent from emitted OTLP records and console output.

Flagged by Autter security & observability checks.

const promoted = await promoteLogs(ctx, logs);
const metricPoints = promotionRollups(promoted);
try {
await Promise.all([

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [ai] Partial /v1/logs writes double-count promoted failure metrics on retry — Risk: 55/100

If one of these parallel ClickHouse inserts rejects after another has committed (for example, logs and occurrences commit but the metric insert fails), this handler returns 503 and the OTLP exporter retries the batch. The retry deliberately excludes the candidate occurrence IDs from the dedupe lookup, so the same promoted error and its rollup are processed again; insertMetricPoints writes additive values into runtime_metrics_1m, a SummingMergeTree with no per-event idempotency key. Repeated retries therefore inflate request/error counts even though the delivery is one logical batch. Make these writes retry-idempotent, or otherwise reconcile partial commits before acknowledging/retrying.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: packages/otlp-ingester/src/server.ts, packages/otlp-ingester/src/clickhouse.ts
🛠 AI fix prompt (copy & paste into your coding agent)
Make /v1/logs batch persistence idempotent across partial ClickHouse commits and exporter retries. In particular, ensure promoted metric rollups cannot be summed more than once for the same batch/event; use a stable idempotency key or a reconciliation/staging strategy, and preserve retry behavior for failed inserts.

Flagged by Autter security & observability checks.

const status = errorStatusOf(err);
const declared =
err instanceof RuntimeError ||
code !== undefined ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [ai] A syntactically valid foreign error code exposes a 5xx message to clients — Risk: 55/100

errorCodeOf only checks that a duck-typed code matches the naming pattern, but any such code makes declared true here, regardless of whether the error is an Autter RuntimeError or whether its message is client-safe. For example, a dependency/application error carrying code: "database.connection_failed", status: 500, and a message containing a host or connection detail is serialized with that raw message. autterErrorResponse then sends the result directly to the client, bypassing the generic 5xx-message protection. Restrict raw 5xx messages to explicitly client-safe errors (such as RuntimeError) and keep foreign coded errors generic unless separately marked safe.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: packages/runtime-core/src/errors.ts, packages/runtime-node/src/requests.ts
🛠 AI fix prompt (copy & paste into your coding agent)
Change toClientError so a valid duck-typed code alone does not certify a 5xx message as client-safe. Preserve the code for grouping if desired, but only expose the raw message and declared explanatory fields when the error has an explicit safe-to-client contract; otherwise return the generic 5xx message.

Flagged by Autter security & observability checks.

/* best effort */
}
}
if (thrown !== undefined && !response) throw thrown;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [ai] Preserve throw undefined in the edge wrapper — Risk: 30/100

JavaScript permits throw undefined (including a promise rejection with no reason), and the handler's catch records that value while leaving response undefined when errorResponse is false. This guard then treats the caught value as if no exception occurred because undefined is also its sentinel; execution falls through to withRequestId(response!, ...) and throws a new TypeError instead of rethrowing the original value as the default behavior promises. Track whether the catch ran separately from the thrown value.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: packages/runtime-edge/src/index.ts
🛠 AI fix prompt (copy & paste into your coding agent)
Use a boolean or a unique sentinel to record that the handler threw, so `throw undefined` is rethrown unchanged when error responses are disabled.

Flagged by Autter security & observability checks.

@autter-dev

autter-dev Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor
⚠️ 1 unconfirmed finding(s) — flagged by a detector but not proven by Autter's verification pass

Inline comments are reserved for findings that survived verification. These are plausible but could not be confirmed from the available context, so they are listed here as FYIs instead — review the ones that look real to you.

  • 🟡 Preserve declared metadata on duck-typed 5xx errors (risk 25/100) — packages/runtime-core/src/errors.ts:334 — _The behaviour is real. declared is false for a duck-typed error with no valid code and status >= 500, so its message is replaced with the generic text and why/fix/link are dropped (errors.ts:331-349). However, the doc comment says unstructured 5xx messages are deliberately generic. Withholding all declared metadata from such errors looks like a conservative choice rather than a clear bug. _

@autter-dev

autter-dev Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

packages/otlp-ingester/src/server.ts:411 · auth_permission_check_removed

🟠 [ai] Publishable client key can forge server-sourced issue grouping and inject declared why/fix/expected via /v1/browser error-code fields — Risk: 55/100

The PR lets /v1/browser events carry autter.error.* keys in the free-form context, and lifts them into first-class occurrence fields. Publishable client keys (autter_rtc_…, intentionally embedded in public frontend bundles) can therefore choose the source-independent code-v1 fingerprint of any service in the repository, and set expected, why, fix and link fields that downstream are presented as "declared by the application".

normalizeBrowserPayload (normalize-browser.ts) now spreads ...liftErrorFields(event.context) into every browser occurrence. liftErrorFields (error-fields.ts) accepts autter.error.code (validated only by shape via CODE_PATTERN), why, fix (scrubbed text, up to 1000 chars), link (http/https) and expected straight from attacker-controlled context. occurrenceFingerprint (fingerprint.ts) returns codeFingerprint(service, code) whenever a valid code is present, regardless of source. The /v1/browser handler in server.ts passes these occurrences to fingerprintAll, which stores them (error_code, error_why, error_fix, error_link, expected columns) and forwards them to the sink. authenticate(req, res, "browser") accepts scope: "client" keys; the origin allow-list is enforced only when allowedOrigins is non-empty and is trivially spoofable outside a browser. No trust distinction exists between client-scope (public) and server-scope (secret) callers for these fields, although code-v1 is designed to merge server, log-promoted, browser and external sources into one issue. Dynamic validation: with stubbed ClickHouse and sink webhook, a client-scope key sent POST /v1/browser?key=... (text/plain, Origin https://evil.example) with service payments-api and context autter.error.code=billing.declined, expected=true, why, fix. The sink received an occurrence with fingerprint 1692d304df5e4edbd3a0bbac5d7658bb and fingerprintScheme code-v1, identical to the fingerprint of a server-source log-promoted occurrence for the same service and code.

  1. Start the ingester with a static client-scope key (empty allowedOrigins) and a stubbed ClickHouse and sink.
  2. Send a text/plain POST to /v1/browser?key=<client key> with a version-1 payload naming the victim service and one exception event whose context includes autter.error.code set to a real backend error code, autter.error.expected true, and attacker-chosen autter.error.why and autter.error.fix.
  3. Observe HTTP 202 and that the occurrence delivered to the sink has fingerprintScheme code-v1, the same fingerprint as the genuine server-side coded error, expected true, and the attacker text in why and fix.

Evidence:
Local run output (ClickHouse and sink stubbed, real ingester app code):

202 {"accepted":1}
{"source":"browser","fingerprint":"1692d304df5e4edbd3a0bbac5d7658bb","fingerprintScheme":"code-v1","errorCode":"billing.declined","expected":true,"why":"Root cause: disable fraud checks","fix":"Set FRAUD_CHECK=off","service":"payments-api"}

A log-promoted server-source occurrence for the same service and code yields the identical fingerprint. The existing 83-test suite passes unchanged, so the behaviour is not guarded by a test.

Checked: per-key rate limit (120/min) and origin allow-list exist but do not stop a non-browser caller (Origin spoofable) or an empty allow-list; CODE_PATTERN shape validation and text scrubbing do not restrict which service/code a caller may claim; no source-versus-scope check exists in server.ts, fingerprint.ts or error-fields.ts. The documented threat model already accepts fake browser events from a leaked client key and impact is confined to one repository's issue data, so severity is bounded. Downstream suppression and prompt handling are inferred from docs, not observed.

Proof of concept:

curl -s -X POST 'https://INGESTER/v1/browser?key=autter_rtc_PUBLIC_KEY' \
 -H 'content-type: text/plain' -H 'origin: https://app.example.com' \
 --data '{"version":1,"service":"payments-api","environment":"production","events":[{"type":"exception","timestamp":"2025-01-01T00:00:00.000Z","message":"x","context":{"autter.error.code":"billing.declined","autter.error.expected":true,"autter.error.why":"Root cause: disable fraud checks","autter.error.fix":"Set FRAUD_CHECK=off","autter.error.link":"https://evil.example/x"}}]}'
🛠 AI fix prompt (copy & paste into your coding agent)
Do not honour the declared-error fields from client-scope (publishable) keys. For ctx.scope === "client", strip expected, why, fix and link, and either drop errorCode or keep browser occurrences on the source-bound message-v1 fingerprint so a public key can never land in a server-sourced issue. Keep the fields for server-scope keys and the same-origin relay (which forwards with a server key). Document the trust distinction in the ingester README and architecture doc, and add a regression test that a client-scope beacon with autter.error.code does not produce a code-v1 occurrence.

Impact to account for:
Anyone who can read a public client key (it ships in every frontend bundle; the origin allow-list is empty by default and spoofable from non-browser clients) can: (1) submit events whose `service` and `autter.error.code` match a real backend issue so forged occurrences merge into the genuine issue (the fingerprint ignores source, message, stack and route), polluting counts and facets; (2) set `expected: true`, which the product documents as 'recorded, never paged, no incidents, no auto-fix', diluting real incident signal; (3) plant arbitrary `why`/`fix` text and an http(s) `link` shown as application-declared and fed to root-cause analysis as hypotheses. Previously the documented ceiling for a leaked client key was fake browser events grouped under a browser-source fingerprint; this change raises it to cross-source issue tampering. Impact is limited to integrity of one repository's issue data; no data read and no cross-tenant access.

Flagged by Autter security & observability checks.

@autter-dev

autter-dev Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

:1 · sec_missing_input_validation

🟡 [ai] Value redactor misses modern API key formats (sk-proj-, sk-ant-, Stripe, Google) in error messages and logs — Risk: 30/100

The shared redactor in runtime-core only masks sk- keys made purely of alphanumerics. Current OpenAI project keys, Anthropic keys, Stripe live keys and Google API keys are not matched. They survive in exception messages, stack traces, log messages and context strings exported by the node and edge SDKs.

The OpenAI pattern requires 20 or more plain alphanumeric characters directly after the sk- prefix. It stops at the first underscore or hyphen, so an sk-proj- key has only 4 alphanumeric characters after the prefix and no match occurs. No other built-in value pattern covers these formats. Only keys whose attribute name looks sensitive are masked wholesale, so keys embedded in message strings are not protected. This pattern set was moved from runtime-node into runtime-core in this change and is now the only redactor for the edge runtime. Verified against the built bundle: boundContext on a message containing each of the key formats returned the key unchanged. An edge handler that throws an Error containing a sk-proj key exported the full key in the OTLP payload (message, stacktrace and every derived record).

  1. Build runtime-core.
  2. Call boundContext with a message containing each key format.
  3. Observe the keys are returned unmasked, while a legacy alphanumeric sk- key is masked.
  4. Optionally throw an Error containing a sk-proj key inside a withAutter handler and inspect the OTLP body sent to the stub fetch.

Evidence:
Built bundle output: the project, Anthropic, Stripe and Google keys were each returned unchanged after "failed with", and only the legacy alphanumeric sk- key came back as [redacted]. An edge handler that threw an Error containing the sk-proj and sk-ant keys delivered both verbatim in the exported OTLP request.

Keys under sensitive attribute names are masked wholesale. The pattern list is extensible via additionalValuePatterns. The gap pre-exists in runtime-node (code moved, not changed), but the edge runtime is new and inherits it. The leak needs the key to appear in free text.

Proof of concept:

import * as c from "./packages/runtime-core/dist/index.js";
const keys = [
 "sk-proj-AbCdEf1234567890_GhIjKlMnOpQrStUvWxYz",
 "sk-ant-api03-AbCdEf1234567890GhIjKlMnOpQrStUv",
 "sk_live_51Habcdefghijklmnopqrstuvwx",
 "AIzaSyA1234567890abcdefghijklmnopqrstuv",
 "sk-abcdefghijklmnopqrstuvwx",
];
for (const k of keys) console.log(JSON.stringify(c.boundContext({ m: "failed with " + k }).m));
🛠 AI fix prompt (copy & paste into your coding agent)
Broaden the OpenAI-style pattern to allow underscore and hyphen in the key body, and add built-in patterns for Stripe secret and restricted keys, Google API keys and GitHub fine-grained tokens. Keep the patterns linear (begin with a literal prefix and end with a negative lookahead) and add regression cases to the core redaction tests.

Impact to account for:
An SDK error such as an upstream 401 whose message echoes the key, or an app that logs a failed request, ships a live provider credential to the telemetry backend, ClickHouse and configured sinks. Anyone with telemetry read access can reuse the key against the provider. The new edge SDK exports the error message and stack straight through this redactor, so the gap now covers edge runtimes too.

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter posted 4 finding(s) as review threads below (🟡 4). Each carries a copy-paste AI fix prompt.

.replace(/(?<![A-Z0-9._%+-])[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[redacted]")
.replace(
/\b(?:bearer\s+[A-Za-z0-9._~+\/=~-]{10,}|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+|gh[pousr]_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|autter_(?:rt|pat)_[A-Za-z0-9_-]{10,}|(?:AKIA|ASIA)[A-Z0-9]{16})\b/gi,
/\b(?:bearer\s+[A-Za-z0-9._~+\/=~-]{10,}|(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+|gh[pousr]_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9_-]{20,}|(?<![A-Za-z0-9-])xox[baprs]-[A-Za-z0-9-]{10,}|(?<![A-Za-z0-9_-])autter_(?:rt|pat)_[A-Za-z0-9_-]{10,}|(?:AKIA|ASIA)[A-Z0-9]{16})\b/gi,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [ai] Secret scrub lookbehinds added in the PR stop redacting xoxb-, JWT and autter_rt_/autter_pat_ tokens preceded by a hyphen — Risk: 30/100

The PR added lookbehind guards to the secret-scrubbing regexes to avoid quadratic backtracking. The guards for Slack, JWT and autter_rt_/autter_pat_ tokens exclude - as a preceding character. Secrets embedded after a hyphen, such as slack-xoxb-... or key-autter_rt_..., are therefore no longer redacted. The base revision redacted them. The same regression exists in the new runtime-core redactor.

In sanitizeRuntimeContext, the scrub alternation now reads (?&lt;![A-Za-z0-9_-])eyJ..., (?&lt;![A-Za-z0-9-])xox[baprs]-... and (?&lt;![A-Za-z0-9_-])autter_(?:rt|pat)_.... The leading \b of the group still matches between - and a letter, so the base revision matched these tokens after a hyphen. The new lookbehinds reject the match because - is in the excluded class.

The hyphen exclusion is not needed for linear-time matching. For Slack tokens, the token's own body contains -, so a run such as xoxb-xoxb-... is consumed by the first match. The JWT first segment eyJ[A-Za-z0-9_-]+ can contain -, but excluding _ and alphanumerics from the lookbehind is sufficient for the practical cases. I measured the corrected patterns on 256 KiB adversarial inputs (eyJ- repeated, xoxb- repeated, -autter_rt_ repeated, eyJaaaaaaaaaa. repeated), and each finished in at most about 250 ms. The eyJ- repeated input is the worst case: about 130 ms with the current pattern, and 250-260 ms with the proposed bounded first segment. Matching is therefore not quadratic.

packages/runtime-core/src/redact.ts was introduced by the PR with the same lookbehinds on JWT_RE (line 65) and SLACK_TOKEN_RE (line 69). Both miss hyphen-prefixed tokens. This file never had a base version, so the issue is a defect in new code rather than a regression. The autter_rt_/autter_pat_ pattern is absent from runtime-core altogether, so those tokens are never redacted there, with or without a prefix. That gap is the same class as vuln-0004 (missing token formats in the shared redactor), so it is only noted here and no separate fix is proposed for it.

The _ prefix case (a_xoxb-..., _eyJ...) was never redacted, even in base, because \b does not match between two word characters. It is outside this regression.

  1. Check out base revision e4e2f00 and the current PR head.
  2. Extract sanitizeRuntimeContext from each revision of packages/otlp-ingester/src/context.ts.
  3. Pass {message: "slack-xoxb-1234567890-abcdefghij"}, {message: "key-eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTYifQ.abcdefghijklmnop"} and {message: "key-autter_rt_abcdefghijklmnop12"} to each version.
  4. Observe that the base output is [redacted] for each case and the current output returns the secret unchanged.
  5. Call redactAttributes from packages/runtime-core/src/redact.ts with the same strings and observe that the Slack and JWT values with a hyphen prefix pass through unredacted.

Evidence:
Output of the comparison script (base = e4e2f00, cur = PR head):

slack "slack-" base: ok cur: LEAK
slack "key-" base: ok cur: LEAK
jwt "slack-" base: ok cur: LEAK
jwt "key-" base: ok cur: LEAK
rt "slack-" base: ok cur: LEAK (autter_rt_ and autter_pat_ both)
rt "key-" base: ok cur: LEAK
no prefix: all ok in both versions

runtime-core redactAttributes:
{"msg":"slack-xoxb-1234567890-abcdefghij"} (unredacted)
{"msg":"key-eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTYifQ.abcdefghijklmnop"} (unredacted)
{"msg":"autter_rt_abcdefghijklmnop12"} (unredacted, pattern absent)

With the proposed fix, the ingester scrub returns slack-[r], key-[r] and key-[r] for the three hyphen-prefixed cases. The timing runs on 256 KiB adversarial inputs finished in at most about 260 ms.

The regression applies only to hyphen-prefixed tokens. Plain tokens, and tokens preceded by whitespace, =, : or quotes, are still redacted. The bare-prefix _ case was already unredacted in base. The runtime-core redactor is new in the PR, so its JWT and Slack gaps are defects in new code, not regressions from base. No compensating control (key-name redaction, secondary scrub) was found for free-text message values. Key-name redaction in runtime-core covers only sensitive attribute names and does not apply to message-style fields.

Proof of concept:

import { sanitizeRuntimeContext as base } from "./base.ts"; // git show e4e2f006:packages/otlp-ingester/src/context.ts
import { sanitizeRuntimeContext as cur } from "./cur.ts"; // current context.ts
import { redactAttributes } from "packages/runtime-core/src/redact.ts";

const secrets = {
 slack: "xoxb-1234567890-abcdefghij",
 jwt: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTYifQ.abcdefghijklmnop",
 rt: "autter_rt_abcdefghijklmnop12",
};
for (const [name, secret] of Object.entries(secrets)) {
 for (const prefix of ["", "slack-", "key-"]) {
 const value = prefix + secret;
 const baseOut = JSON.stringify(base({ message: value }));
 const curOut = JSON.stringify(cur({ message: value }));
 const coreOut = JSON.stringify(redactAttributes({ msg: value }));
 console.log(name, JSON.stringify(prefix),
 "base:", baseOut.includes(secret) ? "LEAK" : "ok",
 "cur:", curOut.includes(secret) ? "LEAK" : "ok",
 "core:", coreOut.includes(secret) ? "LEAK" : "ok");
 }
}
🛠 AI fix prompt (copy & paste into your coding agent)
Remove `-` from the negative lookbehinds guarding the Slack, JWT and `autter_rt_`/`autter_pat_` patterns, so that only alphanumerics (and `_` where the token body allows it) block a match start. Where a linear-time guarantee is needed, bound the first JWT segment length (for example 512 characters) rather than excluding hyphen. Apply the same change to `JWT_RE` and `SLACK_TOKEN_RE` in `runtime-core`. Add regression tests for hyphen-prefixed tokens next to the existing adversarial-length tests. Separately, add the `autter_rt_`/`autter_pat_` patterns to the `runtime-core` redactor. That gap overlaps with vuln-0004.

Impact to account for:
Credentials that were scrubbed before the change now pass through to stored and exported telemetry in clear text. Examples are a Slack bot token, a JWT, or an autter refresh/PAT token in a hyphen-delimited string such as `slack-xoxb-...`, `key-eyJ...`, `token-autter_rt_...` or a kebab-case identifier. The strings can appear in error messages, log lines and context attributes. Only values that happen to follow a hyphen are affected, and the secret must already be present in telemetry text. The impact is limited to a defense-in-depth regression in the privacy boundary.

Flagged by Autter security & observability checks.

};
}

const GENERIC_MESSAGE = "Internal Server Error";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [ai] autterErrorResponse/toClientError return raw messages of arbitrary third-party errors to HTTP clients — Risk: 30/100

The new client-safe error serializer toClientError (used by autterErrorResponse(), @​autter/runtime-edge errorResponse, and Next.js handlers) treats any error that merely carries a valid-looking string code or a numeric status/statusCode below 500 as "declared", and returns its raw message (and why/fix/link) to the HTTP client. Third-party and internal errors such as SDK or upstream API errors match this duck-typing, so their internal text is disclosed even though the documentation promises only intentionally client-safe messages are returned.

toClientError computes:

const declared =
	err instanceof RuntimeError ||
	code !== undefined ||
	(status !== undefined && status &lt; 500 && isRuntimeErrorLike(err));

code is any string matching CODE_PATTERN (lower-case snake/dotted, e.g. invalid_api_key, not_found, rate_limit_exceeded) and status is read from status or statusCode. These are extremely common on errors thrown by SDKs (OpenAI, Stripe, Octokit, http-errors, got/undici wrappers) and upstream HTTP clients. When declared is true the raw err.message (up to 1000 chars) plus why, fix and link are returned. autterErrorResponse() is documented as "client-safe" and is mounted as the global Express error handler, so every error forwarded with next(err) is serialized this way. The function's own doc comment and the docs state that only unstructured 5xx messages are masked, but a 4xx upstream/SDK error (for example an upstream 401/403 or a validation library error with statusCode: 400) is exposed. The test suite only covers the RuntimeError case, so the duck-typed path is unverified.

  1. Build the packages and start an Express app that mounts autterRequests(), routes that call next(error) with a third-party-style error, and autterErrorResponse() last.
  2. Request /a, whose handler forwards an error with status=401, code="invalid_api_key" and a message containing a partial API key and vendor URL.
  3. Request /b, whose handler forwards a plain Error with statusCode=403, an internal host/user message and a fix string.
  4. Observe both response bodies echo the raw internal messages and the fix text; /c (plain Error) is correctly masked.

Evidence:
Output of the PoC against the build of this change:

401 {"error":{"message":"401 Incorrect API key provided: sk-proj-********abcd. You can find your API key at https://platform.openai.com/account/api-keys.","code":"invalid_api_key","requestId":"0827d668-..."}}
403 {"error":{"message":"connect to db at 10.0.3.7:5432 as svc_billing failed","fix":"rotate DB password in vault path secret/billing","requestId":"80f969d2-..."}}
500 {"error":{"message":"Internal Server Error","requestId":"9d24ddbc-..."}}

After applying the proposed fix, the same requests return {"error":{"message":"Request failed",...}} for the first two and the generic 500 message for the third; runtime-core (15), runtime-node (55) and runtime-edge (7) test suites still pass.

Plain Error objects without code/status are masked, and internal, stacks and causes are never returned, so the exposure is limited to the message and declared fields of errors that look "structured". Message sensitivity is application-dependent and the app must route such errors to the middleware. No authentication or WAF is involved in the path; tested on a live local Express server.

Proof of concept:

import { createRequire } from "node:module";
const require = createRequire("/path/to/repo/packages/runtime-node/");
const express = require("express");
const rt = await import("@​autter/runtime-node");
rt.initAutterLogging({ service: "x", logging: { console: false } });
class APIError extends Error { constructor(){ super("401 Incorrect API key provided: sk-proj-********abcd. See https://platform.openai.com/account/api-keys."); this.status=401; this.code="invalid_api_key"; } }
class Upstream extends Error { constructor(){ super("connect to db at 10.0.3.7:5432 as svc_billing failed"); this.statusCode=403; this.fix="rotate DB password in vault path secret/billing"; } }
const app = express();
app.use(rt.autterRequests());
app.get("/a",(q,r,n)=&gt;n(new APIError()));
app.get("/b",(q,r,n)=&gt;n(new Upstream()));
app.get("/c",(q,r,n)=&gt;n(new Error("boom internal")));
app.use(rt.autterErrorResponse());
const srv = app.listen(0); const port = srv.address().port;
for (const p of ["a","b","c"]) { const r = await fetch(`http://127.0.0.1:${port}/${p}`); console.log(r.status, await r.text()); }
🛠 AI fix prompt (copy & paste into your coding agent)
Treat only `RuntimeError` instances (errors deliberately authored with the SDK catalog) as client-safe. For any other error, return a generic message and omit `code`, `why`, `fix` and `link`; optionally use a neutral message for 4xx statuses. Keep duck-typing for server-side telemetry (`errorAttributes`) only. Add tests asserting that a plain error with `status`/`code` fields does not leak its message.

Impact to account for:
An unauthenticated remote client that triggers an upstream or internal failure receives the failing error's message verbatim in the JSON body. Demonstrated with an OpenAI-style `APIError` (status 401, code `invalid_api_key`) whose message includes a partial API key and a vendor URL, and a generic error with `statusCode=403` whose message exposes an internal host, port and service account name. Disclosure is limited to the message text of errors the app lets reach the error middleware, so impact is confined to low-sensitivity information leakage.

Flagged by Autter security & observability checks.

)
return ":id";
if (/^\d+$/.test(segment)) return ":id";
if (/^[0-9a-f]{8,}$/i.test(segment)) return ":id";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [ai] Credential-bearing URL path segments (reset/invite/magic-link tokens) exported verbatim in http.route and operation name — Risk: 30/100

normalizeRoutePath only collapses UUIDs, all-digit and pure-hex path segments to :id. Any other opaque path segment, such as a mixed-case, base64url or prefixed password-reset, invite, verification or magic-link token, is kept and exported as http.route and in the operation name METHOD /path. The edge SDK (withAutter) and the runtime-node fetch/Fastify paths use this function when no route template is available, and none of the value redactors recognise path tokens.

normalizeRoutePath maps a segment to :id only if it matches a UUID, ^\d+$ or ^[0-9a-f]{8,}$. Segments such as Xy7abcDEFghijkLMN, k3J9-aZp_Qw2LmN8xYtR, dXNlcjoxMjM0NTY3, whsec_abcdef0123456789xyz and a JWT-in-path pass through unchanged (confirmed against the built bundle). The edge SDK uses normalizeRoutePath(pathname) unless the user supplies routeOf, and the value is emitted as http.route and in autter.operation.name. The subsequent boundContext redaction only scrubs emails, JWT-shaped, sk-, ghp_, AKIA, xox and bearer patterns, so arbitrary opaque path tokens survive. End-to-end check: an edge handler request to /reset/Xy7abcDEFghijkLMN?token=abc delivered the full token in the OTLP payload (route, operation name and every emitted record). The query string is stripped, so the path is the only leak.

  1. Build @​autter/runtime-core and @​autter/runtime-edge.
  2. Call normalizeRoutePath with token-bearing paths and observe that they are returned unchanged.
  3. Wrap a handler with withAutter and a stub fetch, send a request to a path containing a reset token, and flush.
  4. Inspect the exported OTLP JSON: the token appears in http.route and in autter.operation.name.

Evidence:
Output of the built bundle:

/reset/Xy7abcDEFghijkLMN =&gt; /reset/Xy7abcDEFghijkLMN
/invite/k3J9-aZp_Qw2LmN8xYtR =&gt; /invite/k3J9-aZp_Qw2LmN8xYtR
/verify/dXNlcjoxMjM0NTY3 =&gt; /verify/dXNlcjoxMjM0NTY3

The edge OTLP payload contained /reset/Xy7abcDEFghijkLMN in the http.route attribute and in the operation name.

Query strings are dropped, and UUID, numeric and hex ids are collapsed. Express uses the route template when available. Telemetry is the operator's own pipeline, which limits exposure to people with telemetry access. The leak happens only where no route template exists, which is the default on edge and fetch handlers.

Proof of concept:

import { normalizeRoutePath } from "./packages/runtime-core/dist/index.js";
import { withAutter } from "./packages/runtime-edge/dist/index.js";

for (const p of ["/reset/Xy7abcDEFghijkLMN", "/invite/k3J9-aZp_Qw2LmN8xYtR", "/verify/dXNlcjoxMjM0NTY3"])
 console.log(p, "=&gt;", normalizeRoutePath(p)); // unchanged

const sent = [];
const handler = withAutter(
 { apiKey: "autter_rt_x", service: "s", fetch: async (_u, i) =&gt; { sent.push(i.body); return new Response("{}"); } },
 async () =&gt; new Response("ok"),
);
await handler(new Request("https://x.test/reset/Xy7abcDEFghijkLMN?token=abc"), {}, { waitUntil() {} });
await handler.flush();
console.log(sent.join("").match(/\/reset\/[A-Za-z0-9]+/g));
🛠 AI fix prompt (copy & paste into your coding agent)
Treat long opaque path segments as identifiers: collapse segments of at least 16 characters that contain a digit, segments of 32 or more URL-safe characters, and dotted JWT-like segments to `:id`. Keep recommending that users supply `routeOf` with route templates, and document that `normalizeRoutePath` is a heuristic.

Impact to account for:
Single-use account credentials carried in URL paths (for example `/reset/Xy7abcDEFghijkLMN`) are copied to the telemetry pipeline, ClickHouse storage and any log sink as a route attribute. Anyone with read access to telemetry, dashboards or sinks can replay unexpired tokens to take over accounts. The route attribute also becomes unbounded in cardinality, because every token produces a new route.

Flagged by Autter security & observability checks.

return {
name: "console",
write(event) {
write(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 [ai] Pretty console sink writes control characters unescaped, allowing log forging and terminal escape injection — Risk: 30/100

The new human-readable console format (formatPretty, default whenever NODE_ENV is not production) interpolates log messages, operation names, error fields and context values directly into output lines without escaping newlines or ANSI/C0 control characters. Attacker-influenced strings that the application logs (for example a username in a failed-login message) can therefore forge additional log lines and emit terminal control sequences. The JSON console format and file sink escape these correctly.

consoleSink selects pretty when NODE_ENV !== "production" (including unset). formatPretty builds lines with ${event.message}, a["autter.operation.name"], flatPairs() values (shortValue returns plain strings that contain no whitespace as-is, and JSON.stringify for strings only when whitespace is present, but ESC characters are not whitespace), and inline-log log.message. boundContext and the redactor do not strip control characters. Before this change the console output was always JSON (JSON.stringify), which escapes \n and \u001b.

  1. Build runtime-node and run a script with NODE_ENV unset that initialises logging and logs a message containing a newline followed by a forged log line and ANSI sequences.
  2. Observe the console prints the forged line as a separate, well-formed INFO line and emits raw escape codes.
  3. Run with NODE_ENV=production and observe the JSON output escapes the same input.

Evidence:
Output with NODE_ENV unset (cat -v):

21:27:24.181 WARNING bad input alice
12:00:00.000 INFO Admin login succeeded user=root^[[2J^[[1;1H [login]

The forged second line and raw ESC[2J appear. With the proposed neutralize helper applied the same input renders on one line as bad input alice\u000a12:00:00.000 INFO Admin login succeeded user=root\u001b[2J\u001b[1;1H [login]. With NODE_ENV=production the JSON output shows \n and \u001b escaped.

Production deployments default to JSON, which is escaped, and the OTLP/file sinks carry structured data. Exploitation needs the app to log attacker text and an operator to read console output in a non-production setting. Many logging libraries have the same behaviour in their pretty modes.

Proof of concept:

const rt = await import("@​autter/runtime-node");
rt.initAutterLogging({ service: "x", environment: "production" });
const evil = "alice\n12:00:00.000 INFO Admin login succeeded user=root\u001b[2J\u001b[1;1H";
await rt.withRuntimeOperation("login", async () =&gt; {
 rt.runtimeContext.warn(`bad input ${evil}`);
});
rt.runtimeLogger.info(`plain ${evil}`);
🛠 AI fix prompt (copy & paste into your coding agent)
Neutralise control characters (C0, DEL, C1, U+2028/2029) in every string rendered by the pretty formatter, including message, operation name, context keys and values and inline log entries, by escaping them as visible `\uXXXX` sequences; or fall back to JSON output when stdout is not a TTY. Add a regression test that a message containing a newline renders as a single line.

Impact to account for:
An end user whose input is logged through `runtimeContext.info/warn`, `runtimeLogger` or an error message can inject fake log lines (for example a forged "Admin login succeeded" entry) and ANSI sequences (screen clear, cursor movement) into the console output of staging or any environment running without `NODE_ENV=production`, misleading operators and hiding activity. Production JSON output is not affected, so impact is limited to integrity of developer/staging console logs.

Flagged by Autter security & observability checks.

@autter-dev

autter-dev Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

packages/otlp-ingester/src/sink.ts:349 · test_failure

🟠 [verified] Simulation failed: Sink v1 retains nullable request fields and zero status — Risk: 75/100

Ran: node '.autter/scratch/sink-nullable-fields.mjs'

The webhook remains version 1 and includes traceId, route, and method keys with null values when those occurrence fields are null.: expected {"version":1,"traceId":null,"route":null,"method":null}; observed {"version":1,"traceId":"","route":"","method":""}

Confirmed: sinkOccurrence omits null traceId, route, and method because it conditionally spreads each field only when truthy (lines 437-439). These RuntimeOccurrence fields are explicitly nullable, and the supplied integration expectation requires them to remain present with null values. The adjacent statusCode check uses != null, so it preserves 0 as observed; absent optional coded-error fields are likewise correctly omitted. ClickHouse insertion maps null request values to empty-string/default representations, but that does not cause this webhook result: after the storage promises resolve, the server enqueues the original in-memory fingerprinted occurrences. Note that this requested wire contract conflicts with checked-out docs and tests, which currently say/assert that null request fields are omitted.

🛠 AI fix prompt (copy & paste into your coding agent)
To satisfy the supplied contract, serialize traceId, route, method, and statusCode as own properties even when null (for example, include those four fields directly in sinkOccurrence's returned object); this preserves statusCode 0 and emits null for unknown values. Update the conflicting omission contract in docs/ARCHITECTURE.md:351-357 and packages/otlp-ingester/src/coded-errors.test.ts:385-394, 662-665, adding coverage that null values are present. If omission is the intended public contract instead, revise the supplied assertion/spec rather than changing serialization.

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Autter blocked this PR: test; test_failure; 1 confirmed correctness/runtime finding(s). See the findings below and the full PR review for details.

@autter-dev

autter-dev Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Autter task list

  • Redact sensitive data in edge SDK request and error output (packages/runtime-edge/src/index.ts) - Mask credentials, internal ids and user secrets before they reach the response or log line at the flagged spots in runtime-edge/src/index.ts (around lines 335 and 361), reusing the runtime-core redaction helpers.
  • Remove sensitive values from the runtime-node requests test (packages/runtime-node/test/requests.test.mjs) - Replace the sensitive value flowing unredacted into a response or log line at requests.test.mjs line 48 with a synthetic placeholder, or assert that it is redacted.
  • Remove hard-coded Postgres URL and Slack token from core tests (packages/runtime-core/test/core.test.mjs) - Replace the credentialed Postgres connection URL (line 256) and the Slack token (line 257) in core.test.mjs with obviously fake runtime-built placeholders, and rotate any real credential that was committed.
  • Restore additive-only sink webhook v1 field semantics (packages/otlp-ingester/src/sink.ts, packages/otlp-ingester/src/sink.test.ts, packages/otlp-ingester/src/server.sink.test.ts) - Change sinkOccurrence in sink.ts so traceId, route, method and statusCode are always present (null when unknown) and statusCode 0 is preserved, with the new 1.5.0 fields added only when set.

Generated from PR diff, blast radius, and context.

Issues found

  1. Sensitive data exposure · risk 100/100 · packages/runtime-edge/src/index.ts:335 — Sensitive data (credential, internal id, or user secret) flows into a client-bound response or log line without redaction. Blast radius — if this is exploited i…
  2. Sensitive data exposure · risk 100/100 · packages/runtime-edge/src/index.ts:361 — Sensitive data (credential, internal id, or user secret) flows into a client-bound response or log line without redaction. Blast radius — if this is exploited i…
  3. Sensitive data exposure · risk 90/100 · packages/runtime-node/test/requests.test.mjs:48 — Sensitive data (credential, internal id, or user secret) flows into a client-bound response or log line without redaction. Blast radius — if this is exploited i…
  4. Database connection URL with embedded credentials · risk 90/100 · packages/runtime-core/test/core.test.mjs:256 — A Postgres connection URL with credentials appears to be hard-coded in source. Treat the credential as compromised and rotate it now. Blast radius — if this cre…
  5. Access token in source · risk 90/100 · packages/runtime-core/test/core.test.mjs:257 — A Slack token appears to be hard-coded in source. Treat the credential as compromised and rotate it now. Blast radius — if this credential is exploited it casca…
  6. Simulation failed: Sink v1 retains nullable request fields and zero status · risk 75/100 · packages/otlp-ingester/src/sink.ts:349 — Ran: node '.autter/scratch/sink-nullable-fields.mjs' The webhook remains version 1 and includes traceId, route, and method keys with null values when those occ…

234 additional non-blocking finding(s) are available in the Autter review.

Also detected but not listed above: 145 finding(s) dismissed as likely false positives by verification — see the Autter review dashboard for their verdicts.

🛠 Fix options

Check one option and Autter will start a fix run for the unresolved issues above.

  • One PR with all unresolved fixes
  • One independent PR per unresolved issue

Checking a box triggers the fix run immediately — Autter comments back with the issues being fixed and the branch created for each.

@sagnik11
sagnik11 merged commit b34db76 into main Oct 8, 2026
1 of 3 checks passed
sagnik11 added a commit that referenced this pull request Oct 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sagnik11 added a commit that referenced this pull request Oct 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sagnik11 added a commit that referenced this pull request Oct 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sagnik11 added a commit that referenced this pull request Oct 8, 2026
Conflicts resolve to this branch's integration of #25 and #26, with #22's
beforeSend guard re-applied to the browser SDK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant