Skip to content

fix(repo): comprehensive audit remediation — security, correctness, perf, dead code#124

Merged
Divkix merged 16 commits into
mainfrom
audit-improvements
Jun 8, 2026
Merged

fix(repo): comprehensive audit remediation — security, correctness, perf, dead code#124
Divkix merged 16 commits into
mainfrom
audit-improvements

Conversation

@Divkix

@Divkix Divkix commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Description

Full codebase audit remediation covering ~125 findings across the app, all three SDKs, CI/CD, and config. Changes were grouped into four dependency-ordered waves and verified with all unit + integration + SDK tests passing.

Key highlights:

  • Security: API keys now stored as SHA-256 hashes with migration backfill; auth secret reads from validated env (no hardcoded fallback); in-app token-bucket rate limiting on ingest/login; CSRF checks on SSE stream endpoints; DB password required in prod compose; opencode.yml gated to repo members
  • Correctness (critical): TS SDK flush was sending the entire queue at once — now chunked at batchSize ≤ 100 (server limit); TS SDK fetch had no timeout (hung requests stalled all flushing forever); Go WithMaxRetries/WithHTTPClient were silently ignored; Python on_error deadlock under lock; GIN index replacing unusable btree on FTS column
  • Frontend: SSE hook state promoted to $state so ConnectionStatus actually updates; {#key projectId} resets stream on project navigation; incident detail fetch race fixed; selection tracks by log id (survives column sort)
  • Routes: Timeseries from param validation (was NaN in SQL); streamed export instead of 10k-row in-memory load; MIN_LIMIT 100→1; consistent { error } response shape; COUNT(*) skipped on cursor pages
  • Dead code removed: migrate.ts, createLogStreamStore, formatBucketLabel, showToast/toastInfo/toastWarning, updateInitialFocus, two dead OTLP exports, vitest.workspace.ts, package-lock.json
  • CI hardened: Bun pinned to 1.2.15; component tests and coverage now run in CI; test-migrations job exercises actual drizzle/*.sql files; entrypoint.sh fails fast on migration error; Firefox added to E2E; Dependabot added for all three SDK ecosystems; packages:write scoped to docker jobs only
  • noUncheckedIndexedAccess enabled — ~238 type errors fixed across 22 files

Related Issue

N/A — audit-driven improvements

Potential Risk & Impact

  • API-key hashing: existing plaintext keys are backfilled via migration (SHA-256). The migration handles this automatically; no manual intervention needed.
  • TS SDK shutdown() return type changed (voidPromise<IngestResponse | null>) — technically a breaking change for callers that awaited it.
  • MIN_LIMIT 100→1: any client relying on the 100-floor silent clamp will now get the limit they actually requested.
  • Rate limiting is in-memory per process — resets on restart, not shared across instances.

How Has This Been Tested?

  • bun run check — 0 errors, 0 warnings
  • bun run lint — 0 errors
  • bun run knip — clean
  • bun run test:unit — 334/334 passed
  • bun run test:integration — 309/309 passed
  • sdks/typescript: 159/159 tests passed, tsc --noEmit clean
  • sdks/python: 286/286 unit tests passed
  • sdks/go: go test -race ./... all passed, go vet clean

Divkix added 2 commits June 7, 2026 23:43
Backend:
- BC-1: Replace btree FTS index with GIN for full-text search performance
- BC-2: Skip session DB lookup on /v1/ ingest paths
- BC-3: Fix dead 5xx error sanitization ternary in error-handler
- BC-4: Isolate per-listener errors in event bus emit loops
- BC-5: Delete dead migrate.ts (no callers)
- BC-6: Mark session.ts as test-only with clear JSDoc
- BC-7: Replace count+select+delete cleanup with CTE-based direct delete
- BC-8/9: Drop redundant duplicate indexes
- BC-10: Add explicit DB pool config (max/idle_timeout/connect_timeout)
- BC-14/15: Graceful SIGTERM/SIGINT shutdown + cleanup interval overlap guard
- BC-16: Make log.timestamp NOT NULL
- BC-17: Auth secret from validated env, no hardcoded fallback
- BU-1: Add depth cap (32) to OTLP recursive parser to prevent stack overflow
- BU-2: Add <> to tsquery special-char strip to prevent injection 500s
- BU-4: Store API keys as SHA-256 hashes; backfill migration included
- BU-5/6: Fix CSV \r escaping and formula-injection whitespace bypass
- BU-8/9: Remove dead OTLP exports (logLevelToSeverityNumber, dateToUnixNanoString)
- BU-11: Case-insensitive content-type comparison
- BU-12: Backfill now recomputes incident aggregates for existing incidents
- BU-13: HEX_ID_REGEX requires at least one a-f char (pure numerics → {num})

Routes/API:
- RT-1: Validate timeseries 'from' param to prevent NaN SQL error
- RT-2/5: Add backpressure guard to incidents/stream; fix logs/stream teardown
- RT-3: Add in-memory token-bucket rate limiting on /v1/* and login
- RT-6: Stream export via ReadableStream with 500-row cursor batches
- RT-7: Standardize error response shape via apiError() helper
- RT-8: Skip COUNT(*) on paginated cursor requests
- RT-9: Lower MIN_LIMIT from 100 to 1
- RT-11: Add CSRF check to SSE stream POST handlers
- RT-14: Don't leak raw DB error in health endpoint response

Frontend:
- FE-1: Make SSE hook state reactive () so ConnectionStatus updates
- FE-2: Use {#key projectId} to reset stream on project navigation
- FE-3: Fix incident detail fetch race with request token
- FE-4: Track selection by log id instead of array index (survives sorting)
- FE-5: Scope scroll query to visible layout
- FE-6: AbortController for timeseries refetch race
- FE-8: Track and clear highlight timers on cleanup
- FE-10/11: Add aria-label/title to chart segments for accessibility
- FE-12-15: Remove dead exports (createLogStreamStore, formatBucketLabel, toast helpers, updateInitialFocus)
- FE-16/17/19: Remove needless , void LOG_LEVELS smell, add keys to {#each}

TypeScript SDK:
- TS-1: Chunked flush bounded by batchSize (server limit 100)
- TS-2: Add AbortSignal.timeout to prevent hung fetch stalling all flushes
- TS-3: Add keepalive:true for browser page-unload survival
- TS-4/5: Fix shutdown race; shutdown() returns IngestResponse|null
- TS-6: Re-queue respects maxQueueSize with overflow notification
- TS-7: Add config upper bound validation (batchSize<=100, etc.)
- TS-9: Remove as-cast laundering in config/client
- TS-10: Restrict to http/https; strip trailing slash from endpoint
- TS-11: Child logger skips unused transport allocation
- TS-12: Sync npm and JSR versions
- TS-13: Honor Retry-After header on 429

Python SDK:
- PY-1: Move on_error callbacks outside lock to prevent deadlock
- PY-2: Isolate on_flush exceptions from send try/except (prevent dup delivery)
- PY-3: Double-checked locking for event loop creation
- PY-4: __version__ from importlib.metadata (single source of truth)
- PY-5: Replace inspect.stack() with sys._getframe() (no file I/O per frame)
- PY-6: Add __aenter__/__aexit__ and atexit flush hook
- PY-7/8: Guard response.json(); make timeout configurable
- PY-9: Normalize timestamps to Z suffix
- PY-10: Child skips unused transport allocation
- PY-11-14: URL/scheme hardening, deque queue, mypy 3.9, Retry-After

Go SDK:
- GO-1/2: Wire MaxRetries and HTTPClient into transport; add default 30s timeout
- GO-3: Dispatch batch-threshold flush asynchronously
- GO-4: Log() respects CaptureSourceLocation setting
- GO-5: Fast-path mergeMetadata when no per-call metadata
- GO-6: prepend() enforces MaxQueueSize; Shutdown documents undelivered-log risk
- GO-7: errors.As instead of type assertions
- GO-8: url.JoinPath + scheme validation
- GO-9/10/11/12: Doc fixes, example defer Shutdown, timer generation counter

CI/Config:
- CI-1: Add test-migrations job running actual drizzle/*.sql files
- CI-2: entrypoint.sh fails fast on migration error
- CI-3/4: Run component tests and coverage in CI
- CI-5: Pin bun-version to 1.2.15 across workflows
- CI-6: Run Firefox in CI E2E alongside Chromium
- CI-7: Add Dependabot for all SDK ecosystems
- CI-9: release cancel-in-progress: false
- CI-10: Scope packages:write to docker jobs only
- SEC-1: Add author association gate to opencode workflow
- SEC-3: Require DB_PASSWORD in compose.prod.yaml
- DOCK-3: Increase healthcheck start-period to 60s
- CFG-1: Delete stale vitest.workspace.ts
- CFG-2: Add package-lock.json to .gitignore; delete it
- CFG-3: Enable noUncheckedIndexedAccess; fix all ~238 type errors
- CFG-4: Playwright stderr capture
- CFG-6: Add engines field to package.json
- TEST-1: Fix empty afterEach; add cleanup() call
- TEST-2: New integration tests for regenerate, incident detail, csrf
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Too many files!

This PR contains 243 files, which is 93 over the limit of 150.

To get a review, narrow the scope:
• coderabbit review --type committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 228c352e-af59-45fd-a678-2590c61e23e0

📥 Commits

Reviewing files that changed from the base of the PR and between ab6ca82 and 70d0eb2.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • sdks/typescript/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (243)
  • .github/workflows/ci.yml
  • .github/workflows/opencode.yml
  • .github/workflows/release.yml
  • .github/workflows/sdk-typescript.yml
  • .vite-hooks/pre-commit
  • .zed/settings.json
  • AGENTS.md
  • Dockerfile
  • README.md
  • biome.json
  • compose.yaml
  • drizzle.config.ts
  • drizzle/meta/_journal.json
  • fly.toml
  • package.json
  • playwright.config.ts
  • scripts/__tests__/backfill-incidents.test.ts
  • scripts/__tests__/seed-admin.test.ts
  • scripts/backfill-incidents.ts
  • scripts/seed-admin.ts
  • scripts/tsconfig.json
  • sdks/go/README.md
  • sdks/python/README.md
  • sdks/python/pyproject.toml
  • sdks/python/src/logwell/config.py
  • sdks/python/src/logwell/queue.py
  • sdks/python/tests/unit/test_config.py
  • sdks/typescript/README.md
  • sdks/typescript/biome.json
  • sdks/typescript/package.json
  • sdks/typescript/src/client.ts
  • sdks/typescript/src/config.ts
  • sdks/typescript/src/errors.ts
  • sdks/typescript/src/index.ts
  • sdks/typescript/src/queue.ts
  • sdks/typescript/src/source-location.ts
  • sdks/typescript/src/transport.ts
  • sdks/typescript/src/types.ts
  • sdks/typescript/tests/fixtures/configs.ts
  • sdks/typescript/tests/fixtures/logs.ts
  • sdks/typescript/tests/integration/client.integration.test.ts
  • sdks/typescript/tests/integration/transport.integration.test.ts
  • sdks/typescript/tests/mocks/handlers.ts
  • sdks/typescript/tests/mocks/server.ts
  • sdks/typescript/tests/setup.ts
  • sdks/typescript/tests/unit/client.unit.test.ts
  • sdks/typescript/tests/unit/config.unit.test.ts
  • sdks/typescript/tests/unit/errors.unit.test.ts
  • sdks/typescript/tests/unit/queue.unit.test.ts
  • sdks/typescript/tests/unit/source-location.unit.test.ts
  • sdks/typescript/tests/unit/transport.unit.test.ts
  • sdks/typescript/tsconfig.json
  • sdks/typescript/tsconfig.test.json
  • sdks/typescript/tsup.config.ts
  • sdks/typescript/vite.config.ts
  • sdks/typescript/vitest.config.ts
  • src/app.d.ts
  • src/app.html
  • src/hooks.server.test.ts
  • src/hooks.server.ts
  • src/lib/auth-client.ts
  • src/lib/components/__tests__/accessibility.component.test.ts
  • src/lib/components/__tests__/active-filter-chips.component.test.ts
  • src/lib/components/__tests__/clear-filters-button.component.test.ts
  • src/lib/components/__tests__/connection-status.component.test.ts
  • src/lib/components/__tests__/dashboard-skeleton.component.test.ts
  • src/lib/components/__tests__/incident-status-badge.component.test.ts
  • src/lib/components/__tests__/incident-table.component.test.ts
  • src/lib/components/__tests__/incident-timeline-panel.component.test.ts
  • src/lib/components/__tests__/keyboard-help-modal.component.test.ts
  • src/lib/components/__tests__/level-badge.component.test.ts
  • src/lib/components/__tests__/level-chart.component.test.ts
  • src/lib/components/__tests__/live-toggle.component.test.ts
  • src/lib/components/__tests__/log-card.component.test.ts
  • src/lib/components/__tests__/log-detail-modal.component.test.ts
  • src/lib/components/__tests__/log-row.component.test.ts
  • src/lib/components/__tests__/log-stream-skeleton.component.test.ts
  • src/lib/components/__tests__/log-table.component.test.ts
  • src/lib/components/__tests__/project-card.component.test.ts
  • src/lib/components/__tests__/search-input.component.test.ts
  • src/lib/components/__tests__/stats-skeleton.component.test.ts
  • src/lib/components/__tests__/theme-toggle.component.test.ts
  • src/lib/components/__tests__/time-range-picker.component.test.ts
  • src/lib/components/__tests__/timeseries-chart.component.test.ts
  • src/lib/components/time-range-picker.svelte
  • src/lib/components/ui/button/index.ts
  • src/lib/components/ui/card/index.ts
  • src/lib/components/ui/dropdown-menu/index.ts
  • src/lib/components/ui/input/index.ts
  • src/lib/components/ui/select/index.ts
  • src/lib/components/ui/separator/index.ts
  • src/lib/components/ui/skeleton/index.ts
  • src/lib/components/ui/sonner/index.ts
  • src/lib/components/ui/switch/index.ts
  • src/lib/hooks/__tests__/use-log-stream.component.test.ts
  • src/lib/hooks/use-incident-stream.svelte.ts
  • src/lib/hooks/use-log-stream.svelte.ts
  • src/lib/index.ts
  • src/lib/server/auth.ts
  • src/lib/server/config/env.ts
  • src/lib/server/config/env.unit.test.ts
  • src/lib/server/config/index.ts
  • src/lib/server/config/performance.ts
  • src/lib/server/config/performance.unit.test.ts
  • src/lib/server/config/retention.unit.test.ts
  • src/lib/server/db/README.md
  • src/lib/server/db/db.ts
  • src/lib/server/db/index.ts
  • src/lib/server/db/migrations-journal.unit.test.ts
  • src/lib/server/db/schema.ts
  • src/lib/server/db/test-db.ts
  • src/lib/server/db/test-utils.ts
  • src/lib/server/error-handler.ts
  • src/lib/server/events.ts
  • src/lib/server/events.unit.test.ts
  • src/lib/server/jobs/cleanup-scheduler.ts
  • src/lib/server/jobs/log-cleanup.ts
  • src/lib/server/session.ts
  • src/lib/server/utils/api-error.ts
  • src/lib/server/utils/api-key.ts
  • src/lib/server/utils/api-key.unit.test.ts
  • src/lib/server/utils/auth-guard.ts
  • src/lib/server/utils/content-type.ts
  • src/lib/server/utils/csrf.ts
  • src/lib/server/utils/csv-serializer.ts
  • src/lib/server/utils/csv-serializer.unit.test.ts
  • src/lib/server/utils/cursor.ts
  • src/lib/server/utils/cursor.unit.test.ts
  • src/lib/server/utils/incident-backfill.ts
  • src/lib/server/utils/incident-fingerprint.ts
  • src/lib/server/utils/incident-fingerprint.unit.test.ts
  • src/lib/server/utils/incident-status.unit.test.ts
  • src/lib/server/utils/incidents.ts
  • src/lib/server/utils/incidents.unit.test.ts
  • src/lib/server/utils/otlp.ts
  • src/lib/server/utils/otlp.unit.test.ts
  • src/lib/server/utils/project-guard.ts
  • src/lib/server/utils/rate-limit.unit.test.ts
  • src/lib/server/utils/search.ts
  • src/lib/server/utils/search.unit.test.ts
  • src/lib/server/utils/simple-ingest.ts
  • src/lib/server/utils/simple-ingest.unit.test.ts
  • src/lib/shared/schemas/incident.ts
  • src/lib/shared/schemas/log-level.unit.test.ts
  • src/lib/shared/schemas/log.ts
  • src/lib/shared/schemas/project.ts
  • src/lib/shared/schemas/project.unit.test.ts
  • src/lib/shared/types.ts
  • src/lib/stores/__tests__/logs.unit.test.ts
  • src/lib/stores/logs.svelte.ts
  • src/lib/types/export.ts
  • src/lib/utils.ts
  • src/lib/utils.unit.test.ts
  • src/lib/utils/colors.ts
  • src/lib/utils/colors.unit.test.ts
  • src/lib/utils/focus-trap.ts
  • src/lib/utils/format.ts
  • src/lib/utils/format.unit.test.ts
  • src/lib/utils/keyboard.ts
  • src/lib/utils/keyboard.unit.test.ts
  • src/lib/utils/time-range.ts
  • src/lib/utils/timeseries.ts
  • src/lib/utils/timeseries.unit.test.ts
  • src/lib/utils/toast.ts
  • src/lib/utils/toast.unit.test.ts
  • src/routes/(app)/+layout.server.ts
  • src/routes/(app)/+page.server.ts
  • src/routes/(app)/projects/[id]/+page.server.ts
  • src/routes/(app)/projects/[id]/+page.svelte
  • src/routes/(app)/projects/[id]/incidents/+page.server.ts
  • src/routes/(app)/projects/[id]/settings/+page.server.ts
  • src/routes/(app)/projects/[id]/stats/+page.server.ts
  • src/routes/api/health/+server.ts
  • src/routes/api/projects/+server.ts
  • src/routes/api/projects/[id]/+server.ts
  • src/routes/api/projects/[id]/incidents/+server.ts
  • src/routes/api/projects/[id]/incidents/[incidentId]/+server.ts
  • src/routes/api/projects/[id]/incidents/[incidentId]/timeline/+server.ts
  • src/routes/api/projects/[id]/incidents/stream/+server.ts
  • src/routes/api/projects/[id]/logs/+server.ts
  • src/routes/api/projects/[id]/logs/export/+server.ts
  • src/routes/api/projects/[id]/logs/stream/+server.ts
  • src/routes/api/projects/[id]/regenerate/+server.ts
  • src/routes/api/projects/[id]/stats/+server.ts
  • src/routes/api/projects/[id]/stats/timeseries/+server.ts
  • src/routes/login/+page.server.ts
  • src/routes/login/__tests__/login-navigation.component.test.ts
  • src/routes/v1/ingest/+server.ts
  • src/routes/v1/logs/+server.ts
  • svelte.config.js
  • tests/README.md
  • tests/e2e/auth-guard.spec.ts
  • tests/e2e/dashboard.spec.ts
  • tests/e2e/export-logs.spec.ts
  • tests/e2e/helpers/log-selectors.ts
  • tests/e2e/helpers/otlp.ts
  • tests/e2e/incidents.spec.ts
  • tests/e2e/live-stream.spec.ts
  • tests/e2e/log-stream.spec.ts
  • tests/e2e/login.spec.ts
  • tests/e2e/otlp-ingestion.spec.ts
  • tests/e2e/pagination.spec.ts
  • tests/e2e/project-settings.spec.ts
  • tests/e2e/responsive.spec.ts
  • tests/e2e/stats.spec.ts
  • tests/e2e/theme-toggle.spec.ts
  • tests/fixtures/README.md
  • tests/fixtures/db.ts
  • tests/integration/api/health/health.integration.test.ts
  • tests/integration/api/projects/[id]/logs/stream/server.integration.test.ts
  • tests/integration/api/projects/[id]/stats/timeseries.integration.test.ts
  • tests/integration/api/projects/authorization.integration.test.ts
  • tests/integration/api/projects/csrf.integration.test.ts
  • tests/integration/api/projects/incidents/incident-detail.integration.test.ts
  • tests/integration/api/projects/incidents/incidents.integration.test.ts
  • tests/integration/api/projects/logs/export.integration.test.ts
  • tests/integration/api/projects/logs/logs-query.integration.test.ts
  • tests/integration/api/projects/project-detail.integration.test.ts
  • tests/integration/api/projects/project-rename.integration.test.ts
  • tests/integration/api/projects/regenerate.integration.test.ts
  • tests/integration/api/projects/retention.integration.test.ts
  • tests/integration/api/projects/server.integration.test.ts
  • tests/integration/api/projects/stats/stats.integration.test.ts
  • tests/integration/auth/auth.integration.test.ts
  • tests/integration/db/log.integration.test.ts
  • tests/integration/db/project-retention.integration.test.ts
  • tests/integration/db/project.integration.test.ts
  • tests/integration/hooks/error-handler.integration.test.ts
  • tests/integration/jobs/log-cleanup-ordering.integration.test.ts
  • tests/integration/jobs/log-cleanup.integration.test.ts
  • tests/integration/logs-pagination.test.ts
  • tests/integration/otlp/logs.integration.test.ts
  • tests/integration/server/utils/auth-guard.integration.test.ts
  • tests/integration/simple-ingest/logs.integration.test.ts
  • tests/integration/utils/api-key.integration.test.ts
  • tests/integration/utils/csrf.unit.test.ts
  • tests/integration/utils/incidents.integration.test.ts
  • tests/integration/utils/incidents.upsert.integration.test.ts
  • tests/integration/utils/search.integration.test.ts
  • tests/setup.ts
  • tsconfig.json
  • vite.config.ts
  • vitest.config.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Dependabot entries; pins and tightens GitHub Actions; enforces API-key hashing (migrations, schema, cache, and routes); updates SDK transports/queues and timeouts; hardens server utilities (CSRF, content-type, rate-limit, exports); migrates frontend selection to ID-based and adds API-key reveal modal; adjusts many tests and fixtures.

Changes

Repository automation and runtime config

Layer / File(s) Summary
Dependabot & CI pinning
.github/dependabot.yml, .github/workflows/*
Added SDK-specific Dependabot groups; pinned actions and Bun versions across CI/release workflows; added test-migrations job and moved packages permissions to jobs.
Runtime & container config
Dockerfile, compose.prod.yaml, entrypoint.sh, .gitignore, package.json, playwright.config.ts, tsconfig.json, vitest.workspace.ts
Pinned base images/digests, required DB password, fail-fast migrations/seeding, package version/engines bump, Playwright stderr capture, stricter TS index checks, and test setup changes.

Database, migrations, and API-key flow

Layer / File(s) Summary
Migrations and journal
drizzle/0008_audit_improvements.sql, drizzle/0009_hash_only_api_keys.sql, drizzle/meta/_journal.json
Recreated GIN index, made log.timestamp non-null, introduced/backfilled api_key_hash, then removed plaintext api_key column/unique and default on hash in follow-up migration.
Schema & server DB wiring
src/lib/server/db/schema.ts, src/lib/server/db/index.ts
Project now stores apiKeyHash (unique, not plaintext); log.timestamp set not-null; Postgres client uses explicit pool/timeouts.
API key utilities & routes
src/lib/server/utils/api-key.ts, src/routes/api/projects/*, src/routes/api/projects/[id]/regenerate/*, tests/fixtures and tests referencing API keys
validateApiKey now uses SHA-256 hashed keys with positive/negative caches; invalidate by hash; routes persist only apiKeyHash, return plaintext key only once on creation/regeneration; tests and fixtures updated to use hashed keys and new helpers.

Server utilities, APIs, and jobs

Layer / File(s) Summary
Error/CSRF/content-type helpers
src/lib/server/utils/api-error.ts, src/lib/server/utils/csrf.ts, src/lib/server/utils/content-type.ts
Added apiError helper, clarified CSRF docs/behavior, and tightened JSON Content-Type parsing.
Ingest, streaming, exports, and rate limiting
src/routes/v1/*, src/routes/api/projects/*/logs/*, src/routes/api/projects/*/logs/export/+server.ts, src/lib/server/utils/rate-limit.ts
Added per-project ingest rate limiting, CSRF checks on SSE endpoints, backpressure-aware SSE sending, streaming CSV/JSON exports via ReadableStream, and new in-memory token-bucket limiter.
Jobs and cleanup
src/lib/server/jobs/*
Cleanup scheduler guards against overlapping runs; log cleanup switched to CTE+RETURNING batch deletes.
OTLP, search, incidents, and helpers
src/lib/server/utils/otlp.ts, src/lib/server/utils/search.ts, src/lib/server/utils/incident-backfill.ts
Added recursion depth guards in OTLP parsing, expanded search projection and tsquery sanitization, and recomputation of incident aggregates after backfill/updates.

SDK client and transport updates

Layer / File(s) Summary
Go SDK
sdks/go/logwell/*, sdks/go/examples/*
Introduced async flush goroutine tracking and Shutdown wait, generation-based queue timers, HTTP transport timeout/URL normalization, source capture, and client enqueue/flush behavior; example updated to call Shutdown.
Python SDK
sdks/python/src/logwell/*, sdks/python/pyproject.toml
Added timeout config, HTTP transport URL normalization/retry-after handling, queue deque and safer concurrency, atexit-best-effort shutdown, async context-manager support, and version dynamic resolution.
TypeScript SDK
sdks/typescript/src/*, sdks/typescript/package.json
Added timeout config, stricter endpoint validation, retry-after handling, request timeout/keepalive, queue chunked flush semantics, and adjusted shutdown return types and error fields (retryAfterMs).

Frontend, UI, and app flow

Layer / File(s) Summary
API key UX & pages
src/lib/components/api-key-reveal-modal.svelte, src/lib/components/empty-state-quickstart.svelte, src/routes/(app)/+page.svelte, src/routes/(app)/projects/[id]/settings/*
Added modal to reveal one-time plaintext API keys after creation/regeneration; pages no longer include apiKey in initial payloads; regenerate flow shows transient key; examples default to YOUR_API_KEY.
Log/incident UI and hooks
src/lib/components/log-table.svelte, src/routes/(app)/projects/[id]/*, src/lib/hooks/*
Selection moved from index-based to ID-based (selectedId), highlight timers for new logs, keyboard navigation by ID, SSE cleanup improvements, and Zod validation for incident detail/timeline responses.
Accessibility & small components
src/lib/components/*, src/lib/utils/*
SVG path accessibility, export filename guard, focus-trap safety, timeseries formatter removed, toasts API narrowed.

Tests and fixtures

Layer / File(s) Summary
Fixtures & test helpers
tests/fixtures/db.ts, tests/setup.ts, tsconfig.json
Fixtures now seed apiKeyHash, added seedProjectWithApiKey helper, tests use global DOM cleanup, and TypeScript noUncheckedIndexedAccess enabled.
Integration & unit tests
tests/integration/**, src/lib/**/__tests__/*
Many tests updated to use hashed API keys, non-null assertions, updated expectations for pagination limits, CSRF behavior, health/error messages, streaming/export behavior, and new regenerate/display flows; new incident-detail and regenerate integration suites added.
Vitest workspace
vitest.workspace.ts
Workspace config removed/adjusted to align with test setup changes.

Estimated code review effort: 🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs:

  • Divkix/Logwell#114: Related prior change to db injection timing in src/hooks.server.ts, overlapping with this PR's hooks adjustments.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit-improvements

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
.github/workflows/ci.yml (1)

198-206: ⚠️ Potential issue | 🟠 Major

Install Playwright Firefox + harden CI workflow actions

  • The E2E job runs both --project=chromium and --project=firefox, but the browser install/cache steps only install Chromium (playwright install ... chromium / install-deps chromium), which can break the Firefox run on cold cache. Update the cache key and install both browsers.

    Suggested fix
    -          key: ${{ runner.os }}-playwright-chromium-${{ steps.playwright-version.outputs.version }}
    +          key: ${{ runner.os }}-playwright-browsers-${{ steps.playwright-version.outputs.version }}
    
    -        run: bunx playwright install --with-deps chromium
    +        run: bunx playwright install --with-deps chromium firefox
    
    -        run: bunx playwright install-deps chromium
    +        run: bunx playwright install-deps chromium firefox
  • In test-migrations, actions/checkout@v6 and oven-sh/setup-bun@v2 aren’t pinned to commit SHAs and checkout doesn’t set persist-credentials: false, increasing supply-chain/token exposure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 198 - 206, The Playwright CI steps
only install/cache Chromium which breaks Firefox E2E runs and some actions are
unpinned; update the Playwright cache key (the key using
runner.os-playwright-chromium-${{ steps.playwright-version.outputs.version }})
to include firefox as well, and change the install commands (the steps running
bunx playwright install --with-deps chromium and bunx playwright install-deps
chromium) to install both browsers (chromium and firefox) so cold-cache Firefox
runs succeed; additionally, in the test-migrations job pin actions/checkout@v6
and oven-sh/setup-bun@v2 to stable commit SHAs and set actions/checkout to
persist-credentials: false to reduce token exposure.

Source: Coding guidelines

tests/setup.ts (1)

1-23: ⚠️ Potential issue | 🟠 Major

Fix missing migration bootstrap from tests/setup.ts

  • sdks/typescript/vitest.config.ts configures integration tests with setupFiles: ['./tests/setup.ts'], but tests/setup.ts (lines 1-23) only sets env vars, extends jest-dom matchers, and runs cleanup()—it does not invoke setupTestDatabase()/createTestDatabase() or any drizzle/PGlite migration bootstrapping.
  • Migration/test DB setup is implemented in src/lib/server/db/test-db.ts (createTestDatabase() / setupTestDatabase()), so integration runs need to call it from tests/setup.ts (or the “migrations via tests/setup.ts” contract should be updated).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/setup.ts` around lines 1 - 23, tests/setup.ts currently only sets env
vars and jest-dom matchers but doesn't bootstrap the test migrations; import the
migration helpers from src/lib/server/db/test-db.ts (specifically
setupTestDatabase and/or createTestDatabase) and invoke setupTestDatabase() from
tests/setup.ts before tests run (await if it returns a promise),
handling/logging errors so failures surface; this ensures the drizzle/PGlite
migrations and test DB are created prior to integration tests.

Source: Learnings

tests/integration/db/project.integration.test.ts (1)

40-45: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Cover the hashed API-key contract in the first insert test.

This insert still omits apiKeyHash, so the test passes via schema default instead of validating the new hashing invariant.

Suggested fix
     const [createdProject] = await db
       .insert(project)
       .values({
         id: projectId,
         name: projectName,
         apiKey: apiKey,
+        apiKeyHash: hashApiKey(apiKey),
         ownerId: userId,
       })
       .returning();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/db/project.integration.test.ts` around lines 40 - 45, The
test inserts a project without explicitly providing apiKeyHash so it relies on a
schema default; change the insert in the first insert test to include apiKeyHash
by computing the hash from the apiKey using the same hashing utility used in
production (e.g., hashApiKey or the project's API key hashing function) and pass
that value in the .values({ id, name, apiKey, ownerId, apiKeyHash }) payload;
also update the test assertion to assert the stored apiKeyHash equals the
computed hash to validate the hashing contract.
sdks/go/logwell/client.go (2)

347-375: ⚠️ Potential issue | 🟠 Major

Shutdown(ctx) can block past the caller’s deadline due to flushWG.Wait() waiting on non-cancellable async flushes (sdks/go/logwell/client.go:353-376).

flushWG.Wait() is uninterruptible and waits for goroutines that run c.Flush(context.WithTimeout(context.Background(), 30*time.Second)), so the passed ctx can’t cancel them; this breaks the documented “Respects context cancellation and timeout” contract. Wire the async flush context to Shutdown’s ctx (or cancel them during shutdown), or replace the plain wait with a select on ctx.Done() (and a completion channel) so Shutdown can return when the deadline is reached.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/go/logwell/client.go` around lines 347 - 375, Shutdown currently does an
uninterruptible c.flushWG.Wait() which can block past the caller's ctx because
async flush goroutines use their own non-cancellable context; modify the
shutdown flow so async flushes respect the Shutdown ctx: either (A) change the
async flush goroutines to accept and use a context derived from Shutdown (pass a
ctx from which their Flush calls are made instead of context.Background()/fixed
timeouts), or (B) replace the plain flushWG.Wait() with waiting on a completion
channel that you close when flushes finish and select between that channel and
ctx.Done() so Shutdown returns when ctx is cancelled; update references to
flushWG.Wait(), the async flush spawn site (where goroutines call c.Flush(...)),
and the Flush calls so they use the cancellable context.

181-225: ⚠️ Potential issue | 🔴 Critical

Fix shared-root shutdown coordination for queued log admission and async flush tracking

  • Log/log only check c.shutdown but always enqueue into the shared root queue; a child can still add after root.Shutdown() starts (and potentially call root.flushWG.Add(1) while the parent is waiting), so Shutdown can return while entries remain queued and never flush (timer is stopped on the root).
  • Shutdown(ctx) blocks on c.flushWG.Wait() without considering ctx; the async flush goroutines started from Log/log use context.Background() with a fixed 30s timeout, so caller cancellation/timeout won’t stop shutdown promptly.
  • mergeMetadata fast-path returns the original map (maps[0]) when the other map is empty, so queued entries can alias caller-provided/config metadata maps; if callers mutate those maps after logging, queued entries’ metadata can change (and could race).

Also applies to: log/Shutdown (230-270, 353-375).

🟠 Major comments (25)
src/lib/server/utils/incidents.ts-238-240 (1)

238-240: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid silent skips on missing upsert return rows.

Continuing here can silently omit fingerprint→incident mapping and persist partially linked log batches. Throw to rollback and preserve consistency.

Suggested fix
-    if (!result) continue;
+    if (!result) {
+      throw new Error(`Upsert returned no incident row for fingerprint: ${aggregate.fingerprint}`);
+    }
     incidentByFingerprint.set(aggregate.fingerprint, result);
     touchedIncidents.push(result);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/server/utils/incidents.ts` around lines 238 - 240, The code currently
continues when the upsert returned no row (if (!result) continue;), which can
silently omit the fingerprint→incident mapping and leave partial state; change
this to throw a descriptive error instead of continuing so the caller can
rollback—locate the block that sets incidentByFingerprint
(incidentByFingerprint.set(aggregate.fingerprint, result)) and pushes
touchedIncidents, detect when result is falsy for the given
aggregate.fingerprint, and raise/throw an error (including aggregate.fingerprint
and context) rather than calling continue to ensure the operation fails
atomically.
drizzle/0008_audit_improvements.sql-13-14 (1)

13-14: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Backfill log.timestamp nulls before enforcing NOT NULL.

SET NOT NULL will fail the migration if any legacy rows still have NULL timestamps, which can block rollout. Add a pre-update guard step first.

Suggested migration hardening
 -- BC-16: Make timestamp not-null (safe; defaultNow so no nulls in practice)
+UPDATE "log" SET "timestamp" = NOW() WHERE "timestamp" IS NULL;
 ALTER TABLE "log" ALTER COLUMN "timestamp" SET NOT NULL;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@drizzle/0008_audit_improvements.sql` around lines 13 - 14, Before running
ALTER TABLE "log" ALTER COLUMN "timestamp" SET NOT NULL, add a pre-update step
to backfill any existing NULLs in the log.timestamp column (UPDATE "log" SET
"timestamp" = current_timestamp WHERE "timestamp" IS NULL or equivalent using
your defaultNow) so the NOT NULL change cannot fail; then proceed with the ALTER
and (optionally) ensure the column default is set so future rows get the same
non-null default.
src/lib/server/utils/incident-backfill.ts-115-117 (1)

115-117: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t silently continue when incident creation returns no row.

If this path is hit, logs for that fingerprint can remain unlinked while the transaction still commits partial work. Fail fast so the run is retried cleanly.

Suggested fix
-      if (!created) continue;
+      if (!created) {
+        throw new Error(`Failed to create incident for fingerprint: ${aggregate.fingerprint}`);
+      }
       incidentByFingerprint.set(aggregate.fingerprint, created);
       touchedIncidents.push(created);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/server/utils/incident-backfill.ts` around lines 115 - 117, The code
currently skips when incident creation returns no row (the "if (!created)
continue;" branch), which lets the transaction commit partial work and leaves
logs unlinked; change this to fail-fast by throwing an error (or returning an
explicit failure) that includes the aggregate.fingerprint so the overall run is
retried/aborted instead of continuing. Update the block around
incidentByFingerprint.set, touchedIncidents.push, and the variable created in
the function handling the backfill (look for the create incident call that
assigns created and references aggregate.fingerprint) to throw a descriptive
Error when created is falsy rather than using continue.
src/routes/v1/logs/+server.ts-83-98 (1)

83-98: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Early resourceLogs heuristic can reject valid payloads.

resourceLogs.length is not the same as log-record count; this check can return 400 batch_too_large for requests that would pass the real records.length limit. Remove this pre-check or compute nested record count before rejecting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/v1/logs/`+server.ts around lines 83 - 98, The early heuristic
using body.resourceLogs.length can falsely reject valid requests; update the
check to compute the actual nested record count instead of using
resourceLogs.length (or remove the pre-check). Specifically, iterate the nested
structure under body.resourceLogs -> scopeLogs -> logRecords (or otherwise parse
into the final records array used later) and sum the actual log record count,
then compare that total against API_CONFIG.BATCH_INSERT_LIMIT before returning
the 400 response; ensure you reference and replace the current block that
examines body and resourceLogs to perform this accurate count.
.github/workflows/opencode.yml-32-33 (1)

32-33: ⚠️ Potential issue | 🟠 Major

Pin anomalyco/opencode/github to an immutable commit SHA (remove @latest).

In .github/workflows/opencode.yml the workflow uses anomalyco/opencode/github@latest, which can change behavior if the tag is moved.

Suggested fix
-        uses: anomalyco/opencode/github@latest
+        uses: anomalyco/opencode/github@<40-char-commit-sha>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/opencode.yml around lines 32 - 33, The workflow currently
uses anomalyco/opencode/github@latest which is mutable; update the uses entry in
.github/workflows/opencode.yml to reference an immutable 40-character commit SHA
(anomalyco/opencode/github@<40-char-sha>) instead of `@latest`, replacing the
string "anomalyco/opencode/github@latest" with the pinned commit SHA and commit
that change so the action version is fixed and reproducible.
.github/workflows/ci.yml-298-303 (1)

298-303: ⚠️ Potential issue | 🟠 Major

Pin GitHub Actions by commit SHA and disable checkout credential persistence
In .github/workflows/ci.yml (test-migrations, lines ~298-303), actions/checkout@v6 and oven-sh/setup-bun@v2 are still unpinned, and persist-credentials is not set anywhere in the workflow (so checkout can retain git credentials by default). Same unpinned actions/checkout@v6 / oven-sh/setup-bun@v2 also appear in other jobs (e.g., build, docker-build, docker-publish).

Suggested hardening
       - name: Checkout code
-        uses: actions/checkout@v6
+        uses: actions/checkout@<full-commit-sha>
+        with:
+          persist-credentials: false

       - name: Setup Bun
-        uses: oven-sh/setup-bun@v2
+        uses: oven-sh/setup-bun@<full-commit-sha>
         with:
           bun-version: "1.2.15"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 298 - 303, Pin the workflow actions
and disable checkout credential persistence: replace loose pins
'actions/checkout@v6' and 'oven-sh/setup-bun@v2' with their respective commit
SHAs (use the latest verified commit SHAs) wherever they appear (e.g., in the
test-migrations, build, docker-build, docker-publish jobs) and add
persist-credentials: false under the checkout step's with block to prevent Git
credential leakage; ensure the same change is applied to all instances of
actions/checkout and oven-sh/setup-bun in the workflow.

Source: Linters/SAST tools

src/lib/server/utils/api-key.ts-48-49 (1)

48-49: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bound and prune the negative cache to avoid memory blowups.

NEGATIVE_CACHE is unbounded and expired entries are not deleted on read. A flood of unique invalid keys can keep growing memory pressure.

Suggested patch
 const NEGATIVE_CACHE = new Map<string, NegativeCacheEntry>();
+const MAX_NEGATIVE_CACHE_SIZE = 5000;

+function setNegativeCache(keyHash: string): void {
+  const now = Date.now();
+  // prune expired first
+  for (const [k, v] of NEGATIVE_CACHE) {
+    if (v.expiresAt <= now) NEGATIVE_CACHE.delete(k);
+  }
+  // enforce bound
+  if (NEGATIVE_CACHE.size >= MAX_NEGATIVE_CACHE_SIZE) {
+    const oldest = NEGATIVE_CACHE.keys().next().value;
+    if (oldest !== undefined) NEGATIVE_CACHE.delete(oldest);
+  }
+  NEGATIVE_CACHE.set(keyHash, { expiresAt: now + NEGATIVE_CACHE_TTL_MS });
+}
   const negCached = NEGATIVE_CACHE.get(keyHash);
-  if (negCached && negCached.expiresAt > Date.now()) {
+  if (negCached && negCached.expiresAt > Date.now()) {
     throw new ApiKeyError(401, 'Invalid API key');
   }
+  if (negCached && negCached.expiresAt <= Date.now()) {
+    NEGATIVE_CACHE.delete(keyHash);
+  }
-    NEGATIVE_CACHE.set(keyHash, { expiresAt: Date.now() + NEGATIVE_CACHE_TTL_MS });
+    setNegativeCache(keyHash);

Also applies to: 149-152, 170-172

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/server/utils/api-key.ts` around lines 48 - 49, NEGATIVE_CACHE
currently is an unbounded Map of NegativeCacheEntry objects which can grow
indefinitely; change it to a bounded cache (LRU or capped-size Map) and ensure
reads prune expired entries: when looking up NEGATIVE_CACHE (the places that
read/write the map), first remove entries whose expiration has passed and return
miss for expired entries, and when inserting, evict the
oldest/least-recently-used entry if the cap (e.g., 1000 entries) is exceeded;
update all lookup/insertion sites to use these semantics so expired entries are
cleaned on access and total entries remain bounded.
src/routes/v1/ingest/+server.ts-60-63 (1)

60-63: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep 429 payload aligned with the endpoint’s error envelope.

Line 60 returns only { error }, while this route otherwise returns { error, message }. That creates a contract mismatch for clients.

Suggested patch
-    return new Response(JSON.stringify({ error: 'rate_limited' }), {
-      status: 429,
-      headers: { 'Content-Type': 'application/json', 'Retry-After': '60' },
-    });
+    return json(
+      { error: 'rate_limited', message: 'Rate limit exceeded. Retry in 60 seconds.' },
+      { status: 429, headers: { 'Retry-After': '60' } },
+    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/v1/ingest/`+server.ts around lines 60 - 63, The 429 response
currently returns only { error: 'rate_limited' } which breaks the route's error
envelope; update the Response body to include both error and message (e.g., {
error: 'rate_limited', message: 'Too many requests, try again later' }) while
leaving the status 429 and headers (Content-Type and Retry-After) intact so
clients receive the same error shape as other failures in this route.
Dockerfile-15-16 (1)

15-16: ⚠️ Potential issue | 🟠 Major

Pin the Bun base image to an exact version + digest (no floating tags).
Dockerfile line 16 uses the floating tag oven/bun:1-alpine, so the runtime image can drift. (CI/release already pins Bun to 1.2.15 via oven-sh/setup-bun.)

Suggested patch
- FROM oven/bun:1-alpine AS base
+ FROM oven/bun:1.2.15-alpine@sha256:<resolved_digest> AS base
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` around lines 15 - 16, The Dockerfile uses a floating Bun tag in
the FROM instruction ("FROM oven/bun:1-alpine AS base"); replace that floating
tag with an exact version + digest (e.g., the same version used in CI like
1.2.15) so the base stage ("base") is pinned for reproducible builds—fetch the
correct sha256 digest for oven/bun:1.2.15-alpine from the registry and update
the FROM line to use oven/bun:1.2.15-alpine@sha256:<digest>; also check and pin
any other Bun image references in the Dockerfile to the same
exact-version@sha256 form.
src/hooks.server.ts-51-59 (1)

51-59: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Set event.locals.db before the fast-path resolve(event) return.

Routes on skipped paths can miss DB injection because execution returns before locals are populated.

Suggested fix
   await ensureInitialized();
 
+  // Keep DB available on all server routes, including fast-path routes
+  event.locals.db = db;
+
   // Skip session lookup for paths that never need auth
   const pathname = event.url.pathname;
   if (
     pathname.startsWith('/v1/') ||
     pathname === '/api/health' ||
     pathname.startsWith('/static/')
   ) {
     return resolve(event);
   }
@@
-  // Test injection seam — tests override this with a PGlite client via locals.db
-  event.locals.db = db;

Also applies to: 72-73

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks.server.ts` around lines 51 - 59, Fast-path early returns (the
resolve(event) branches guarded by pathname checks) are happening before
event.locals.db is populated, so populate event.locals.db (the DB injection used
elsewhere) before any early return; locate the fast-path that checks
event.url.pathname and the other similar fast-path around the session logic,
ensure you initialize and assign event.locals.db (or call the helper that does
so) prior to calling resolve(event) in both places so skipped routes still get
the DB injected.
entrypoint.sh-14-14 (1)

14-14: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail startup when db:seed exits non-zero under ADMIN_PASSWORD.

This currently masks real seed failures and can boot with partially initialized auth state.

Suggested fix
-  bun run db:seed || echo "Seed step failed (admin may already exist, continuing)"
+  if ! bun run db:seed; then
+    echo "Seed step failed. Aborting startup."
+    exit 1
+  fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@entrypoint.sh` at line 14, The seed step currently swallows failures by
appending "|| echo ..." — update entrypoint.sh so that when ADMIN_PASSWORD is
set (non-empty) you run "bun run db:seed" and if it returns non-zero the script
exits with a non-zero status (failing startup); if ADMIN_PASSWORD is not set,
preserve the previous tolerant behavior (log or echo the failure and continue).
Locate the line invoking "bun run db:seed" in entrypoint.sh and gate the
exit-on-failure behavior on the ADMIN_PASSWORD environment variable.
sdks/go/logwell/queue.go-97-101 (1)

97-101: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Surface overflow when prepend drops re-queued entries.

This new truncation path silently discards logs when a failed flush is re-queued into an already-full buffer. The normal add overflow path reports that loss via OnError; this path should do the same, otherwise transport failures can drop data with no callback or signal to the caller.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/go/logwell/queue.go` around lines 97 - 101, When combining re-queued
entries into q.entries the truncation path (where combined := append(entries,
q.entries...) and combined = combined[:q.maxQueueSize]) silently drops older
items; modify this block to detect when len(combined) > q.maxQueueSize and,
before slicing, call q.OnError (or the same overflow reporting used by add) with
a clear message and the count of dropped entries so re-queued/log-loss is
surfaced; keep q.entries assignment and slicing behavior but ensure the overflow
callback is invoked when truncation happens.
sdks/go/examples/basic/main.go-44-44 (1)

44-44: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle the deferred shutdown error.

client.Shutdown returns an error here, and the unchecked return is already breaking the SDK Go lint job.

Suggested fix
-	defer client.Shutdown(context.Background())
+	defer func() {
+		if err := client.Shutdown(context.Background()); err != nil {
+			log.Printf("failed to shut down Logwell client: %v", err)
+		}
+	}()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/go/examples/basic/main.go` at line 44, The deferred call to
client.Shutdown in main (client.Shutdown(context.Background())) ignores its
error; change the defer to capture and handle the returned error — e.g. wrap it
in a deferred closure that calls client.Shutdown with a context and checks the
error, then logs or returns it (for example in main use defer func(){ if err :=
client.Shutdown(ctx); err != nil { log.Printf("client.Shutdown error: %v", err)
}}()). Locate the client.Shutdown call in main and replace the plain defer with
such an error-checking deferred closure.

Sources: Linters/SAST tools, Pipeline failures

sdks/go/logwell/client.go-385-399 (1)

385-399: ⚠️ Potential issue | 🟠 Major

Fix metadata aliasing in mergeMetadata fast-paths
mergeMetadata returns the original map reference for len(maps) == 1 and for len(maps) == 2 && len(maps[1]) == 0, so queued LogEntry.Metadata can alias caller-owned maps. Since queued entries are later JSON-marshaled during flush, mutating those maps after enqueue (or concurrently) can both change already-buffered log entries and trigger concurrent map read and map write.
Clone non-empty input maps before returning from these fast paths (including the “second map is empty/nil” case) so each queued log entry owns its metadata.

Suggested direction
-	// Fast-path: single map, return directly (read-only, safe)
+	// Fast-path: single empty map
 	if len(maps) == 1 {
 		if len(maps[0]) == 0 {
 			return nil
 		}
-		return maps[0]
+		result := make(map[string]any, len(maps[0]))
+		for k, v := range maps[0] {
+			result[k] = v
+		}
+		return result
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/go/logwell/client.go` around lines 385 - 399, mergeMetadata currently
returns original map references in the fast-paths (when len(maps)==1 and when
len(maps)==2 && len(maps[1])==0), which can cause metadata aliasing and
concurrent mutation issues; change these fast-paths in mergeMetadata to return a
shallow copy (allocate a new map and copy all key/value pairs) of the non-empty
input map instead of the original so queued LogEntry.Metadata owns its data and
mutations won't affect queued entries or cause concurrent map access panics.
sdks/go/logwell/transport.go-46-50 (1)

46-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Default timeout fallback is bypassed for default configs.

At Line 48, timeout fallback only applies when cfg.HTTPClient is nil, but newDefaultConfig initializes HTTPClient to http.DefaultClient (sdks/go/logwell/config.go, Line 165). That means default clients can still run without a request timeout and hang network calls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/go/logwell/transport.go` around lines 46 - 50,
newHTTPTransportFromConfig currently only sets a timeout when cfg.HTTPClient is
nil, but newDefaultConfig sets HTTPClient to http.DefaultClient so requests can
still have no timeout; update newHTTPTransportFromConfig to detect when
cfg.HTTPClient is nil, equals http.DefaultClient, or has Timeout == 0 and in
those cases create and use a new *http.Client with Timeout: 30*time.Second
(carry over Transport if present) so default configs get a safe request timeout;
reference symbols: newHTTPTransportFromConfig, Config, HTTPClient,
http.DefaultClient, httpClient.
src/routes/api/projects/[id]/regenerate/+server.ts-44-47 (1)

44-47: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Plaintext API key is still being stored.

Line 45 persists apiKey: newApiKey in addition to apiKeyHash on Line 46. Keeping raw credentials in DB negates the security benefit of hash-based key storage if the database is exposed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/api/projects/`[id]/regenerate/+server.ts around lines 44 - 47, The
handler is persisting the plaintext apiKey field alongside apiKeyHash (apiKey:
newApiKey and apiKeyHash: hashApiKey(newApiKey)); remove storing the raw
credential from the update payload and only persist apiKeyHash and updatedAt in
the DB (keep hashApiKey(newApiKey) and updatedAt: new Date()), then return the
newly generated plaintext apiKey to the caller in the HTTP response (not in the
DB) so callers can see it once while the database only stores the hash.
src/routes/api/projects/[id]/incidents/stream/+server.ts-41-45 (1)

41-45: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t close SSE stream on backpressure (Lines 41-45).

Backpressure currently returns false, and Lines 57/83 treat any false as terminal and call cleanup(). That disconnects slow clients instead of only dropping an event.

Proposed fix
-      const sendEvent = (eventName: string, data: string): boolean => {
-        if (isClosed) return false;
+      const sendEvent = (
+        eventName: string,
+        data: string,
+      ): 'sent' | 'backpressure' | 'closed' => {
+        if (isClosed) return 'closed';
         try {
           const size = (controller as ReadableStreamDefaultController).desiredSize;
           if (size !== null && size <= 0) {
-            // Stream backpressure: slow consumer, drop the event
-            return false;
+            return 'backpressure';
           }
           controller.enqueue(encoder.encode(formatSSEEvent(eventName, data)));
-          return true;
+          return 'sent';
         } catch {
-          // Controller closed
-          return false;
+          return 'closed';
         }
       };
@@
-          const success = sendEvent('incidents', JSON.stringify(batch));
-          if (!success) cleanup();
+          const result = sendEvent('incidents', JSON.stringify(batch));
+          if (result === 'closed') cleanup();
           batch = [];
@@
-        const success = sendEvent('heartbeat', JSON.stringify({ ts: Date.now() }));
-        if (!success) cleanup();
+        const result = sendEvent('heartbeat', JSON.stringify({ ts: Date.now() }));
+        if (result === 'closed') cleanup();

Also applies to: 56-58, 82-84

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/api/projects/`[id]/incidents/stream/+server.ts around lines 41 -
45, The backpressure branch should not return false (which callers treat as
terminal and call cleanup()); change the desiredSize check block (the
controller.desiredSize branch) to return true to indicate "event dropped but
keep stream open", and update the callers that currently treat a false enqueue
result as terminal (the code that calls cleanup()) so they only call cleanup on
an explicit terminal signal (e.g., a dedicated TERMINAL constant, null/undefined
error, or an isTerminal flag) rather than on a boolean false; reference the
controller.desiredSize check and cleanup() call sites to make these coordinated
changes.
sdks/typescript/src/queue.ts-109-127 (1)

109-127: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let shutdown() return success after a later chunk fails.

If an early chunk succeeds and a later chunk fails, this code requeues the failed logs and then returns lastResponse. During shutdown(), stopped is already true, so those requeued entries never get another timer-driven flush. The caller sees success while logs are left unsent.

Suggested direction
         } catch (error) {
           // Re-queue failed batch at front, respect maxQueueSize
           const requeued = [...batch, ...this.queue];
           this.queue.length = 0;
           this.queue.push(...requeued.slice(0, this.config.maxQueueSize));
           if (requeued.length > this.config.maxQueueSize) {
             this.config.onError?.(
               new LogwellError(
                 `Queue overflow: dropped ${requeued.length - this.config.maxQueueSize} logs`,
                 'QUEUE_OVERFLOW',
               ),
             );
           }
-          this.config.onError?.(error as Error);
-          break; // stop flushing on error
+          const normalizedError = error instanceof Error ? error : new Error(String(error));
+          this.config.onError?.(normalizedError);
+          throw normalizedError;
         }

At minimum, shutdown() needs to fail if any chunk remains queued after its final flush attempt.

Also applies to: 130-156

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/typescript/src/queue.ts` around lines 109 - 127, The flush logic in
shutdown/send loop allows an earlier successful response (lastResponse) to be
returned even if a later chunk failed and was requeued, leaving logs unsent when
stopped is true; update the flush/error handling in the send loop (referencing
sendBatch, lastResponse, this.queue, maxQueueSize, onError, onFlush) so that any
time a batch fails and entries are requeued the shutdown sequence treats it as a
fatal error — e.g., record a failure flag or throw a LogwellError and ensure
shutdown does not return lastResponse successfully when this.queue is non-empty
after the final flush attempt (or propagate the caught error) so callers receive
a failure if any logs remain unsent.
sdks/typescript/src/config.ts-114-145 (1)

114-145: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate timeout before merging it into ResolvedConfig.

timeout is the only numeric option added here that skips the validation block. validateConfig({ timeout: 0 }), -1, or NaN currently succeeds and returns a “resolved” config with an unusable timeout.

Suggested fix
   if (config.flushInterval !== undefined && config.flushInterval > 60000) {
     throw new LogwellError('flushInterval cannot exceed 60000ms', 'INVALID_CONFIG');
   }
+
+  if (
+    config.timeout !== undefined &&
+    (!Number.isFinite(config.timeout) || config.timeout <= 0)
+  ) {
+    throw new LogwellError('timeout must be a positive finite number', 'INVALID_CONFIG');
+  }
 
   // Normalize endpoint: strip trailing slash
   const endpoint = config.endpoint.replace(/\/$/, '');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/typescript/src/config.ts` around lines 114 - 145, The code fails to
validate the numeric "timeout" option before returning ResolvedConfig, allowing
0, negative, or NaN values; add a validation block similar to the other numeric
checks that runs before merging: if (config.timeout !== undefined &&
(!Number.isFinite(config.timeout) || config.timeout < 0)) throw new
LogwellError('timeout must be a finite non-negative number', 'INVALID_CONFIG');
then continue to use timeout: config.timeout ?? DEFAULT_CONFIG.timeout when
building the returned config object (references: config.timeout, DEFAULT_CONFIG,
ResolvedConfig, and LogwellError).
sdks/typescript/src/transport.ts-83-89 (1)

83-89: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Parse HTTP-date Retry-After values instead of parseFloat only.

Retry-After is not always a number. When the header is an HTTP date, parseFloat(...) yields NaN, but Line 83 still treats that as “present” and retries with an effectively immediate sleep right when the server is rate-limiting.

Suggested fix
   private createErrorWithRetryAfter(response: Response, message: string): LogwellError {
     const { status } = response;
     if (status === 429) {
       const retryAfterHeader = response.headers.get('Retry-After');
-      const retryAfterMs = retryAfterHeader ? parseFloat(retryAfterHeader) * 1000 : undefined;
+      const retryAfterMs = retryAfterHeader
+        ? (() => {
+            const seconds = Number(retryAfterHeader);
+            if (Number.isFinite(seconds)) return seconds * 1000;
+
+            const retryAt = Date.parse(retryAfterHeader);
+            return Number.isFinite(retryAt) ? Math.max(0, retryAt - Date.now()) : undefined;
+          })()
+        : undefined;
       return new LogwellError(
         `Rate limited: ${message}`,
         'RATE_LIMITED',
         status,
         true,

Also applies to: 147-158

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/typescript/src/transport.ts` around lines 83 - 89, The Retry-After
handling currently treats any non-NaN value as numeric and may end up treating
HTTP-date headers as present with NaN/0 delay; update the parsing logic that
sets lastError.retryAfterMs so it supports both delta-seconds and HTTP-date
formats (use Number(...) or parseInt for numeric delta and Date.parse(...) for
HTTP-date, then convert to milliseconds and compute relative ms from now), then
in the retry branch use that ms (capped by the existing backoff ceiling) when
calling sleep; apply the same parsing/fallback change to the other Retry-After
handling block referenced around lines 147-158 so both places set
lastError.retryAfterMs consistently (refer to symbols lastError.retryAfterMs,
attempt, sleep, delay).
src/lib/stores/__tests__/logs.unit.test.ts-4-24 (1)

4-24: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

This test no longer validates store behavior.

Lines 4–24 only assert a local object literal and do not exercise the store implementation. This can pass even if the logs store runtime behavior is broken, so please restore behavioral tests for the store API (e.g., add/clear/update/reset semantics).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/stores/__tests__/logs.unit.test.ts` around lines 4 - 24, The test
currently only asserts a local ClientLog literal and must be replaced with
behavioral tests that exercise the logs store API: import the logs store (e.g.,
createLogsStore or logsStore) and call its methods to verify addLog (or add),
getLogs/getAll (or list), updateLog (or update), clearLogs (or clear) and
resetLogs (or reset) semantics; write assertions that after addLog the store
returns the item via getLogs, after updateLog the stored entry is changed, and
after clearLogs/resetLogs the store is empty. Ensure you reference the ClientLog
shape when constructing test entries and use the actual exported function names
from the logs store module present in this repo.
src/lib/server/utils/rate-limit.ts-9-10 (1)

9-10: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate RPM env values before using them as bucket capacity.

Number(...) can yield NaN/invalid capacities, which can break or bypass rate limiting. Clamp to a finite positive integer before use.

Suggested fix
+function parsePositiveRpm(raw: string | undefined, fallback: number): number {
+  const n = Number(raw);
+  return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
+}
+
-export const INGEST_RPM = Number(process.env.RATE_LIMIT_INGEST_RPM ?? 600); // 600 req/min per key
-export const LOGIN_RPM = Number(process.env.RATE_LIMIT_LOGIN_RPM ?? 10); // 10 req/min per IP
+export const INGEST_RPM = parsePositiveRpm(process.env.RATE_LIMIT_INGEST_RPM, 600); // 600 req/min per key
+export const LOGIN_RPM = parsePositiveRpm(process.env.RATE_LIMIT_LOGIN_RPM, 10); // 10 req/min per IP

 export function checkRateLimit(key: string, rpm: number): boolean {
+  const capacity = Number.isFinite(rpm) && rpm > 0 ? Math.floor(rpm) : 1;
   const now = Date.now();
-  const bucket = buckets.get(key) ?? { tokens: rpm, last: now };
+  const bucket = buckets.get(key) ?? { tokens: capacity, last: now };
   const elapsed = (now - bucket.last) / 60000; // minutes
-  bucket.tokens = Math.min(rpm, bucket.tokens + elapsed * rpm);
+  bucket.tokens = Math.min(capacity, bucket.tokens + elapsed * capacity);
   bucket.last = now;

Also applies to: 21-33

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/server/utils/rate-limit.ts` around lines 9 - 10, INGEST_RPM and
LOGIN_RPM are parsed directly with Number(...) and may become NaN/invalid;
validate and clamp each env value to a finite positive integer before using as
bucket capacity (e.g., parse with Number, if !Number.isFinite(value) or value <=
0 fallback to the default, then Math.floor() to an integer and optionally clamp
to a sensible max). Update the exports for INGEST_RPM and LOGIN_RPM to implement
this validation/clamping logic and apply the same pattern to the other exported
rate-limit constants in this file so no exported RPM can be NaN, non-finite,
zero, negative, or non-integer.
src/routes/api/projects/+server.ts-143-149 (1)

143-149: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid storing plaintext API keys in the database.

This still writes the raw apiKey to project, which leaves credentials recoverable if DB data is exposed. Store only apiKeyHash and return generatedApiKey directly in the create response.

Suggested fix
   const generatedApiKey = generateApiKey();
   const newProject = {
     id: nanoid(),
     name,
-    apiKey: generatedApiKey,
     apiKeyHash: hashApiKey(generatedApiKey),
     ownerId: user.id,
   };

   const [created] = await db.insert(project).values(newProject).returning();
   if (!created) return apiError(500, 'internal_error', 'Failed to create project');

   return json(
     {
       id: created.id,
       name: created.name,
-      apiKey: created.apiKey,
+      apiKey: generatedApiKey,
       createdAt: created.createdAt?.toISOString(),
       updatedAt: created.updatedAt?.toISOString(),
     },
     { status: 201 },
   );

Also applies to: 160-160

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/api/projects/`+server.ts around lines 143 - 149, The code is
persisting the plaintext generatedApiKey on the newProject object; remove the
apiKey property from the project record and only store apiKeyHash (use
hashApiKey(generatedApiKey)), then include the plaintext generatedApiKey only in
the API response returned by the handler (do not write it to DB). Update any
other creation paths that set newProject.apiKey (e.g., the other occurrence
noted) to follow the same pattern so only apiKeyHash is stored and
generatedApiKey is returned once to the client.
tests/integration/jobs/log-cleanup-ordering.integration.test.ts-5-25 (1)

5-25: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Integration test no longer validates the behavior it claims.

This now passes without asserting ordering/cleanup semantics (it only checks that execute was called with empty rows). In tests/integration, this should exercise real DB behavior (PGlite) or be moved/renamed as a unit-style mock test.

As per coding guidelines, “Place integration tests in tests/integration/ directory using PGlite in-memory database via @electric-sql/pglite.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/jobs/log-cleanup-ordering.integration.test.ts` around lines
5 - 25, The test claims to validate ordering/cleanup semantics but only uses a
mocked DB and thus doesn't exercise real behavior; update the test around
cleanupOldLogs to either (A) become a true integration test using an in-memory
PGlite DB (create the logs table, insert rows with different ids/created_at and
retentionDays, call cleanupOldLogs(db) and assert deletes occur in deterministic
order and totalLogsDeleted/errors) or (B) move/rename this file to a unit test
location and explicitly mock and assert ordering expectations on the mocked
db.execute/select calls; reference the cleanupOldLogs function, the
executeMock/db test doubles, and the existing expectations to implement the
correct approach.

Source: Coding guidelines

tests/fixtures/db.ts-74-80 (1)

74-80: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent apiKey/apiKeyHash desynchronization in project fixture construction.

...overrides is applied after computing apiKeyHash, so a provided overrides.apiKeyHash can overwrite the derived hash and create inconsistent seeded data.

Suggested fix
 export function createProjectFactory(
   overrides: Partial<ProjectInsert> & { ownerId: string },
 ): ProjectInsert {
-  const apiKey = overrides.apiKey ?? generateApiKey();
+  const base = {
+    id: nanoid(),
+    name: `test-project-${nanoid(8)}`,
+    ...overrides,
+  };
+  const apiKey = base.apiKey ?? generateApiKey();
   return {
-    id: nanoid(),
-    name: `test-project-${nanoid(8)}`,
+    ...base,
     apiKey,
     apiKeyHash: createHash('sha256').update(apiKey).digest('hex'),
-    ...overrides,
   };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/fixtures/db.ts` around lines 74 - 80, The fixture currently computes
apiKeyHash before spreading overrides, allowing overrides.apiKeyHash to
desynchronize the pair; fix by excluding apiKeyHash from the overrides and
computing the hash from the final apiKey (which may come from overrides.apiKey)
so the returned object always has a consistent pair. Concretely, destructure
overrides to remove apiKeyHash (e.g. const { apiKeyHash: _ignoredHash,
...safeOverrides } = overrides), determine apiKey = safeOverrides.apiKey ??
generateApiKey(), compute apiKeyHash =
createHash('sha256').update(apiKey).digest('hex'), and then return an object
that spreads safeOverrides plus the computed apiKey and apiKeyHash so overrides
cannot overwrite the derived hash.
🟡 Minor comments (6)
scripts/backfill-incidents.ts-2-7 (1)

2-7: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Comment advertises a --project flag that the script does not implement.

This will mislead operators during incident backfills. Either implement --project parsing or remove that instruction from the header.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/backfill-incidents.ts` around lines 2 - 7, The header mentions a
--project flag that isn't implemented; add CLI parsing in
scripts/backfill-incidents.ts to accept an optional --project (id or slug)
argument (use process.argv, yargs, or commander), validate it, and when
iterating projects (e.g., in main() or wherever getProjects()/backfillProject()
are used) filter to only that project before running the backfill; ensure the
flag is optional, preserve idempotency, and update the header comment to reflect
the implemented flag usage (or remove the mention if you choose not to implement
it).
compose.prod.yaml-11-13 (1)

11-13: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the section label: DB_PASSWORD is required, not optional.

Line 12 conflicts with the section header and enforced ${DB_PASSWORD:?...} usage, which can confuse operators.

Suggested patch
-# Optional Environment Variables:
-#   DB_PASSWORD - Database password (required, no default for security)
+# Required Environment Variables:
+#   DB_PASSWORD      - Database password (required, no default for security)
+# Optional Environment Variables:
 #   PORT        - App port (default: 3000)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@compose.prod.yaml` around lines 11 - 13, Update the YAML comment header to
reflect that DB_PASSWORD is required (not optional) and remove the contradictory
"Optional Environment Variables" label; explicitly mark DB_PASSWORD as required
and keep PORT as optional with its default. Locate the block mentioning
DB_PASSWORD and PORT (the commented section referencing DB_PASSWORD and PORT)
and change the section label and wording so it reads something like "Environment
Variables" or "Required Environment Variables" for DB_PASSWORD while leaving
PORT noted as optional/default, ensuring the comment matches the enforced
`${DB_PASSWORD:?...}` usage.
sdks/python/src/logwell/client.py-118-118 (1)

118-118: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix Ruff E501 on Line 118.

Line 118 exceeds the configured max length and is currently breaking the Python lint/type-check workflow.

Proposed fix
-            "timestamp": entry.get("timestamp") or datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z'),
+            "timestamp": entry.get("timestamp")
+            or datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),

As per coding guidelines, sdks/python/src/**/*.py: "Use ruff linter and mypy type checker for Python SDK code quality."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/python/src/logwell/client.py` at line 118, Line 118 is too long; extract
the timestamp expression into a short intermediate variable before constructing
the dict to satisfy Ruff E501. For example, compute a variable like timestamp =
entry.get("timestamp") or
datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z') and then use
"timestamp": timestamp in the dict; look for the occurrence of
entry.get("timestamp"), datetime.now, timezone.utc, and isoformat().replace to
find where to change.

Sources: Coding guidelines, Pipeline failures

sdks/python/src/logwell/transport.py-256-259 (1)

256-259: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve Ruff SIM105 at Lines 256-259.

This try/except/pass block is failing lint; convert to contextlib.suppress(ValueError).

Proposed fix
+from contextlib import suppress
@@
-                if retry_after_header:
-                    try:
-                        err.retry_after = float(retry_after_header)  # type: ignore[attr-defined]
-                    except ValueError:
-                        pass
+                if retry_after_header:
+                    with suppress(ValueError):
+                        err.retry_after = float(retry_after_header)  # type: ignore[attr-defined]

As per coding guidelines, sdks/python/src/**/*.py: "Use ruff linter and mypy type checker for Python SDK code quality."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/python/src/logwell/transport.py` around lines 256 - 259, Replace the
try/except/pass around parsing retry_after_header with
contextlib.suppress(ValueError): wrap the assignment err.retry_after =
float(retry_after_header) inside a contextlib.suppress(ValueError) block (or add
"from contextlib import suppress" and use "with suppress(ValueError): ...");
also add the import if missing in sdks/python/src/logwell/transport.py so the
linter (Ruff SIM105) is satisfied and the behavior of the code (silently
ignoring ValueError) remains the same.

Sources: Coding guidelines, Pipeline failures

src/lib/server/utils/incidents.unit.test.ts-73-75 (1)

73-75: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert row existence before dereferencing updatedIncident.

At Line 73, using updatedIncident! can turn an empty-result root cause into a less-informative property access failure. Add an explicit existence assertion before field checks.

Suggested change
     const [updatedIncident] = await db
       .select()
       .from(incident)
       .where(eq(incident.projectId, projectId));

+    expect(updatedIncident).toBeDefined();
     expect(updatedIncident!.firstSeen.toISOString()).toBe('2026-03-01T12:00:00.000Z');
     expect(updatedIncident!.lastSeen.toISOString()).toBe('2026-03-02T12:00:00.000Z');
     expect(updatedIncident!.totalEvents).toBe(2);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/server/utils/incidents.unit.test.ts` around lines 73 - 75, The test
currently dereferences updatedIncident with non-null assertion
(updatedIncident!) which can hide a missing-row failure; before asserting fields
like firstSeen/lastSeen/totalEvents, add an explicit existence check for the
variable (e.g., expect(updatedIncident).toBeDefined() or toBeTruthy()) so the
test fails with a clear message if the query returned no result; update the
assertions in incidents.unit.test.ts around the updatedIncident symbol to first
assert its presence and only then access its properties.
src/lib/server/utils/content-type.ts-9-9 (1)

9-9: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use exact media type matching instead of prefix matching.

The current check allows non-JSON media types like application/jsonp. Parse the media type token and compare exactly to application/json.

Suggested fix
 export function requireJsonContentType(request: Request): Response | null {
   const contentType = request.headers.get('content-type') ?? '';
-  if (!contentType.toLowerCase().startsWith('application/json')) {
+  const mediaType = contentType.split(';', 1)[0]?.trim().toLowerCase();
+  if (mediaType !== 'application/json') {
     return json(
       { error: 'unsupported_media_type', message: 'Content-Type must be application/json' },
       { status: 415 },
     );
   }
   return null;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/server/utils/content-type.ts` at line 9, The check in content-type.ts
using contentType.toLowerCase().startsWith('application/json') is too permissive
(e.g., allows application/jsonp); instead extract the media type token from
contentType (split on ';', trim, toLowerCase()) and compare it for exact
equality to 'application/json'. Update the conditional that references the
contentType variable to use the parsed media type token and treat missing/empty
contentType defensively (reject if no valid media type).

CI blockers:
- Go: check Shutdown error in basic example (errcheck); rewrite queue.prepend to
  avoid gocritic appendAssign and surface re-queue overflow via OnError
- Python: fix ruff E501 (client.py) and SIM105 (transport.py); fix mypy --strict
  errors in config.py; apply ruff format
- E2E: run chromium only (the firefox project was never installed, causing the
  20-min job timeout/cancellation)

Security — store API keys hashed only:
- Drop the plaintext project.api_key column (migration 0009); store and look up
  by api_key_hash only; return the plaintext key once at create/regenerate
- Add a one-time key-reveal modal on create; settings shows the key only right
  after regeneration; invalidate the key cache by hash

SDK correctness:
- Go: ctx-aware Shutdown, shared-root shutdown race fix, clone merged metadata,
  apply default HTTP client timeout
- TS: propagate flush failure during shutdown, validate timeout, parse HTTP-date
  Retry-After values

App correctness/security:
- Throw instead of silently skipping a missing incident upsert/insert row
- Bound the API-key negative cache; validate rate-limit RPM env values; match
  content-type exactly; inject locals.db before fast-path routes
- Accurate OTLP batch-size check; consistent 429 error envelope; SSE backpressure
  no longer disconnects slow clients

Infra hardening:
- Pin all GitHub Actions to commit SHAs + persist-credentials: false
- Pin the Bun base image to 1.2.15-alpine@sha256; fail startup on seed error when
  ADMIN_PASSWORD is set; backfill NULL log.timestamp before SET NOT NULL

Tests: migrate the suite to hash-only keys; convert log-cleanup ordering test to
real PGlite; assorted assertion hardening.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
tests/fixtures/db.ts (1)

74-79: ⚡ Quick win

Use the shared API-key hashing utility in fixtures to avoid contract drift.

createHash('sha256') is duplicated in fixture code; importing the same hashApiKey utility used by runtime routes keeps tests aligned if hashing behavior changes later.

Suggested refactor
-import { createHash } from 'node:crypto';
+import { hashApiKey } from '../../src/lib/server/utils/api-key';
@@
-  const apiKeyHash =
-    overrides.apiKeyHash ?? createHash('sha256').update(generateApiKey()).digest('hex');
+  const apiKeyHash = overrides.apiKeyHash ?? hashApiKey(generateApiKey());
@@
-  const apiKeyHash = createHash('sha256').update(apiKey).digest('hex');
+  const apiKeyHash = hashApiKey(apiKey);

Also applies to: 152-153

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/fixtures/db.ts` around lines 74 - 79, Replace the inline hashing in the
fixture that computes apiKeyHash with the shared hashing utility to avoid
duplication: instead of calling
createHash('sha256').update(generateApiKey()).digest('hex') when building the
apiKeyHash (and the similar code around generateApiKey at the later occurrence),
call the central hashApiKey(...) helper with the plain API key produced by
generateApiKey(); ensure you import or reference hashApiKey and use it wherever
apiKeyHash is derived so tests follow the runtime hashing implementation.
sdks/typescript/src/queue.ts (1)

147-175: 💤 Low value

Edge case: concurrent flush during shutdown may cause premature return.

If flush() is already in progress when shutdown() is called, the spliced batch is temporarily removed from the queue. shutdown() may see queue.length === 0 at line 155 and return null before flush() completes—missing a re-queue if that flush fails.

This is a narrow timing window and the improvement over the previous silent-failure behavior is valuable, so this is low priority. If you want to close the gap later, consider awaiting flushing to settle before the early-return check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/typescript/src/queue.ts` around lines 147 - 175, In shutdown(), avoid
the race where an in-progress flush temporarily empties the queue by awaiting
any current flush promise before checking queue length; update the Shutdown
logic in the shutdown method to, after stopTimer() and before `if
(this.queue.length === 0)`, await the instance's in-flight flush indicator
(e.g., `this.flushing` or `this._flushPromise`) to settle so that a concurrently
running flush can re-queue failed items before shutdown returns or throws;
ensure you reference the existing `flush()` and the `queue`/`stopped` members
when implementing this await.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 43-45: The CI pull_request workflow is using the
oven-sh/setup-bun@... steps without disabling cache; update every setup step
that uses oven-sh/setup-bun (the steps that currently set bun-version: "1.2.15")
to include no-cache: true in their with block so dependency cache restoration is
disabled for untrusted PRs — add no-cache: true alongside bun-version in each
oven-sh/setup-bun step in the pull_request job.

In @.github/workflows/release.yml:
- Around line 46-48: The release workflow uses oven-sh/setup-bun (the Setup Bun
steps) which caches the Bun executable by default; update every Setup Bun step
(the uses: oven-sh/setup-bun@... blocks) to include no-cache: true to disable
executable caching (add the no-cache input to the Setup Bun steps at each
occurrence), and tighten the actions/cache key used for bun pm cache so it
includes the release identifier (tag or commit SHA) in the key to scope/avoid
cache poisoning when restoring Bun dependency artifacts.

In @.github/workflows/sdk-typescript.yml:
- Around line 45-47: Replace all instances of the Bun setup input that currently
use "bun-version: latest" with the pinned version "1.2.15"; specifically update
each block that uses the action "uses:
oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6" by changing the
"bun-version" value from latest to "1.2.15" (there are multiple occurrences in
the sdk-typescript.yml workflow).

In `@src/lib/components/api-key-reveal-modal.svelte`:
- Around line 22-26: The document-level Escape handler (handleKeyDown) currently
calls handleClose even when the modal is closed; update the handler to first
check the modal's open state (e.g., the open prop/variable) and return early if
open is false, or alternatively only add the keydown listener while open is
true; specifically modify handleKeyDown to guard with "if (!open) return" before
checking event.key === 'Escape' (and apply the same guard/update where the
listener is registered/removed).

In `@tests/integration/jobs/log-cleanup-ordering.integration.test.ts`:
- Around line 13-18: The per-test database created by setupTestDatabase() in
beforeEach isn't torn down; capture the returned setup object (e.g., assign the
result of setupTestDatabase() to a top-level variable like "setup" alongside
"db") and add an afterEach that awaits setup.cleanup() (guarded for existence)
to close DB handles; reference the existing beforeEach and the
setupTestDatabase/cleanup symbols and ensure db is set from setup.db so the
teardown can reliably run after each test.

---

Nitpick comments:
In `@sdks/typescript/src/queue.ts`:
- Around line 147-175: In shutdown(), avoid the race where an in-progress flush
temporarily empties the queue by awaiting any current flush promise before
checking queue length; update the Shutdown logic in the shutdown method to,
after stopTimer() and before `if (this.queue.length === 0)`, await the
instance's in-flight flush indicator (e.g., `this.flushing` or
`this._flushPromise`) to settle so that a concurrently running flush can
re-queue failed items before shutdown returns or throws; ensure you reference
the existing `flush()` and the `queue`/`stopped` members when implementing this
await.

In `@tests/fixtures/db.ts`:
- Around line 74-79: Replace the inline hashing in the fixture that computes
apiKeyHash with the shared hashing utility to avoid duplication: instead of
calling createHash('sha256').update(generateApiKey()).digest('hex') when
building the apiKeyHash (and the similar code around generateApiKey at the later
occurrence), call the central hashApiKey(...) helper with the plain API key
produced by generateApiKey(); ensure you import or reference hashApiKey and use
it wherever apiKeyHash is derived so tests follow the runtime hashing
implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f5afcac-0c64-4574-83de-7b4f95ba7406

📥 Commits

Reviewing files that changed from the base of the PR and between 07ae9f8 and bd3470d.

⛔ Files ignored due to path filters (1)
  • sdks/python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (58)
  • .github/workflows/ci.yml
  • .github/workflows/opencode.yml
  • .github/workflows/release.yml
  • .github/workflows/sdk-go.yml
  • .github/workflows/sdk-python.yml
  • .github/workflows/sdk-typescript.yml
  • Dockerfile
  • compose.prod.yaml
  • drizzle/0008_audit_improvements.sql
  • drizzle/0009_hash_only_api_keys.sql
  • drizzle/meta/_journal.json
  • entrypoint.sh
  • scripts/backfill-incidents.ts
  • sdks/go/examples/basic/main.go
  • sdks/go/logwell/client.go
  • sdks/go/logwell/queue.go
  • sdks/go/logwell/transport.go
  • sdks/python/src/logwell/client.py
  • sdks/python/src/logwell/config.py
  • sdks/python/src/logwell/transport.py
  • sdks/typescript/src/config.ts
  • sdks/typescript/src/queue.ts
  • sdks/typescript/src/transport.ts
  • src/hooks.server.ts
  • src/lib/components/api-key-reveal-modal.svelte
  • src/lib/components/empty-state-quickstart.svelte
  • src/lib/components/log-table.svelte
  • src/lib/server/db/schema.ts
  • src/lib/server/utils/api-key.ts
  • src/lib/server/utils/content-type.ts
  • src/lib/server/utils/incident-backfill.ts
  • src/lib/server/utils/incidents.ts
  • src/lib/server/utils/incidents.unit.test.ts
  • src/lib/server/utils/rate-limit.ts
  • src/routes/(app)/+page.svelte
  • src/routes/(app)/projects/[id]/+page.server.ts
  • src/routes/(app)/projects/[id]/+page.svelte
  • src/routes/(app)/projects/[id]/settings/+page.server.ts
  • src/routes/(app)/projects/[id]/settings/+page.svelte
  • src/routes/(app)/projects/[id]/stats/+page.server.ts
  • src/routes/api/projects/+server.ts
  • src/routes/api/projects/[id]/+server.ts
  • src/routes/api/projects/[id]/incidents/stream/+server.ts
  • src/routes/api/projects/[id]/regenerate/+server.ts
  • src/routes/v1/ingest/+server.ts
  • src/routes/v1/logs/+server.ts
  • tests/e2e/project-settings.spec.ts
  • tests/fixtures/db.ts
  • tests/integration/api/projects/authorization.integration.test.ts
  • tests/integration/api/projects/project-detail.integration.test.ts
  • tests/integration/api/projects/project-rename.integration.test.ts
  • tests/integration/api/projects/regenerate.integration.test.ts
  • tests/integration/api/projects/server.integration.test.ts
  • tests/integration/db/project.integration.test.ts
  • tests/integration/jobs/log-cleanup-ordering.integration.test.ts
  • tests/integration/otlp/logs.integration.test.ts
  • tests/integration/simple-ingest/logs.integration.test.ts
  • tests/integration/utils/api-key.integration.test.ts
💤 Files with no reviewable changes (6)
  • scripts/backfill-incidents.ts
  • src/routes/(app)/projects/[id]/settings/+page.server.ts
  • tests/integration/utils/api-key.integration.test.ts
  • src/routes/(app)/projects/[id]/stats/+page.server.ts
  • src/routes/(app)/projects/[id]/+page.server.ts
  • src/routes/(app)/projects/[id]/+page.svelte
✅ Files skipped from review due to trivial changes (2)
  • .github/workflows/sdk-python.yml
  • src/lib/components/empty-state-quickstart.svelte
🚧 Files skipped from review as they are similar to previous changes (21)
  • src/lib/server/utils/content-type.ts
  • tests/integration/api/projects/project-rename.integration.test.ts
  • src/routes/api/projects/[id]/incidents/stream/+server.ts
  • src/routes/v1/ingest/+server.ts
  • src/routes/api/projects/+server.ts
  • sdks/go/examples/basic/main.go
  • drizzle/0008_audit_improvements.sql
  • src/hooks.server.ts
  • tests/integration/otlp/logs.integration.test.ts
  • drizzle/meta/_journal.json
  • src/lib/server/utils/incident-backfill.ts
  • Dockerfile
  • sdks/python/src/logwell/client.py
  • sdks/go/logwell/transport.go
  • src/lib/server/utils/rate-limit.ts
  • sdks/typescript/src/transport.ts
  • compose.prod.yaml
  • sdks/typescript/src/config.ts
  • tests/integration/api/projects/regenerate.integration.test.ts
  • sdks/go/logwell/queue.go
  • sdks/python/src/logwell/transport.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Integration Tests
  • GitHub Check: Unit Tests
  • GitHub Check: Docker Build
🧰 Additional context used
📓 Path-based instructions (17)
.github/workflows/{ci,release,sdk-*}.yml

📄 CodeRabbit inference engine (AGENTS.md)

Configure GitHub Actions workflows for CI (lint, test, build, Docker), releases, and SDK publishing

Files:

  • .github/workflows/sdk-typescript.yml
  • .github/workflows/sdk-go.yml
  • .github/workflows/release.yml
  • .github/workflows/ci.yml
{drizzle.config.ts,drizzle/**/*.sql}

📄 CodeRabbit inference engine (AGENTS.md)

Configure database migrations in drizzle.config.ts and store migration files in drizzle/ folder

Files:

  • drizzle/0009_hash_only_api_keys.sql
src/lib/server/**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

Keep server-only code in src/lib/server/ directory and use Drizzle queries from src/lib/server/db/

Files:

  • src/lib/server/utils/incidents.ts
  • src/lib/server/utils/incidents.unit.test.ts
  • src/lib/server/utils/api-key.ts
  • src/lib/server/db/schema.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Enable import organization in Biome configuration for consistent module import ordering

Files:

  • src/lib/server/utils/incidents.ts
  • src/routes/api/projects/[id]/regenerate/+server.ts
  • tests/integration/api/projects/server.integration.test.ts
  • tests/integration/jobs/log-cleanup-ordering.integration.test.ts
  • src/lib/server/utils/incidents.unit.test.ts
  • tests/integration/api/projects/authorization.integration.test.ts
  • tests/fixtures/db.ts
  • src/lib/server/utils/api-key.ts
  • tests/e2e/project-settings.spec.ts
  • src/routes/v1/logs/+server.ts
  • tests/integration/api/projects/project-detail.integration.test.ts
  • sdks/typescript/src/queue.ts
  • src/lib/server/db/schema.ts
  • src/routes/api/projects/[id]/+server.ts
  • tests/integration/db/project.integration.test.ts
  • tests/integration/simple-ingest/logs.integration.test.ts
src/routes/api/**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

Define API endpoints in src/routes/api/ directory with appropriate HTTP method handlers

Files:

  • src/routes/api/projects/[id]/regenerate/+server.ts
  • src/routes/api/projects/[id]/+server.ts
tests/integration/**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

Place integration tests in tests/integration/ directory using PGlite in-memory database via @electric-sql/pglite

Files:

  • tests/integration/api/projects/server.integration.test.ts
  • tests/integration/jobs/log-cleanup-ordering.integration.test.ts
  • tests/integration/api/projects/authorization.integration.test.ts
  • tests/integration/api/projects/project-detail.integration.test.ts
  • tests/integration/db/project.integration.test.ts
  • tests/integration/simple-ingest/logs.integration.test.ts
**/*.unit.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

Collocate unit tests with source files using *.unit.test.ts naming convention

Files:

  • src/lib/server/utils/incidents.unit.test.ts
src/routes/(app)/**/*.svelte

📄 CodeRabbit inference engine (AGENTS.md)

Use authenticated route structure with (app)/ directory for protected routes

Files:

  • src/routes/(app)/+page.svelte
  • src/routes/(app)/projects/[id]/settings/+page.svelte
**/*.svelte

📄 CodeRabbit inference engine (AGENTS.md)

Disable Biome rules for Svelte files: noUnusedImports, noUnusedVariables, useConst, useImportType

Files:

  • src/routes/(app)/+page.svelte
  • src/lib/components/api-key-reveal-modal.svelte
  • src/lib/components/log-table.svelte
  • src/routes/(app)/projects/[id]/settings/+page.svelte
tests/fixtures/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Store test fixtures in tests/fixtures/ directory for reuse across test suites

Files:

  • tests/fixtures/db.ts
tests/e2e/**/*.spec.ts

📄 CodeRabbit inference engine (AGENTS.md)

Place E2E tests in tests/e2e/ directory using Playwright with Chromium and Firefox browsers

Files:

  • tests/e2e/project-settings.spec.ts
{Dockerfile,docker-compose.yml,.github/workflows/release.yml}

📄 CodeRabbit inference engine (AGENTS.md)

Build multi-platform Docker images for linux/amd64 and linux/arm64 architectures on release

Files:

  • .github/workflows/release.yml
src/routes/v1/**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ingest endpoints with API key authentication in src/routes/v1/ directory

Files:

  • src/routes/v1/logs/+server.ts
sdks/typescript/**

📄 CodeRabbit inference engine (AGENTS.md)

Use tsup bundler for TypeScript SDK compilation with output to dist/ folder containing CJS, ESM, and type definitions

Files:

  • sdks/typescript/src/queue.ts
sdks/python/**

📄 CodeRabbit inference engine (AGENTS.md)

Use hatchling build system for Python SDK with entry point at sdks/python/src/logwell/__init__.py

Files:

  • sdks/python/src/logwell/config.py
sdks/python/src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use ruff linter and mypy type checker for Python SDK code quality

Files:

  • sdks/python/src/logwell/config.py
sdks/go/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

sdks/go/**/*.go: Define Go SDK module at github.com/Divkix/Logwell/sdks/go with entry package in logwell/ directory
Use golangci-lint for Go SDK static analysis and gofmt for formatting

Files:

  • sdks/go/logwell/client.go
🧠 Learnings (17)
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to .github/workflows/{ci,release,sdk-*}.yml : Configure GitHub Actions workflows for CI (lint, test, build, Docker), releases, and SDK publishing

Applied to files:

  • .github/workflows/sdk-typescript.yml
  • .github/workflows/sdk-go.yml
  • .github/workflows/release.yml
  • .github/workflows/ci.yml
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Run pre-commit checklist with `bun run lint && bun run check && bun run knip` before committing code

Applied to files:

  • .github/workflows/sdk-typescript.yml
  • .github/workflows/release.yml
  • .github/workflows/ci.yml
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to sdks/go/**/*.go : Use golangci-lint for Go SDK static analysis and gofmt for formatting

Applied to files:

  • .github/workflows/sdk-go.yml
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to src/routes/v1/**/*.{ts,js} : Implement ingest endpoints with API key authentication in `src/routes/v1/` directory

Applied to files:

  • src/routes/api/projects/[id]/regenerate/+server.ts
  • src/routes/v1/logs/+server.ts
  • src/routes/api/projects/[id]/+server.ts
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to src/routes/api/**/*.{ts,js} : Define API endpoints in `src/routes/api/` directory with appropriate HTTP method handlers

Applied to files:

  • src/routes/api/projects/[id]/regenerate/+server.ts
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to src/lib/server/**/*.{ts,js} : Keep server-only code in `src/lib/server/` directory and use Drizzle queries from `src/lib/server/db/`

Applied to files:

  • src/routes/api/projects/[id]/regenerate/+server.ts
  • tests/integration/api/projects/server.integration.test.ts
  • src/lib/server/db/schema.ts
  • src/routes/api/projects/[id]/+server.ts
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to vitest.config.ts : Use `vitest.config.ts` configuration for unit and integration test projects

Applied to files:

  • tests/integration/api/projects/server.integration.test.ts
  • tests/integration/jobs/log-cleanup-ordering.integration.test.ts
  • tests/integration/api/projects/authorization.integration.test.ts
  • tests/e2e/project-settings.spec.ts
  • tests/integration/db/project.integration.test.ts
  • tests/integration/simple-ingest/logs.integration.test.ts
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to tests/integration/**/*.test.ts : Place integration tests in `tests/integration/` directory using PGlite in-memory database via `electric-sql/pglite`

Applied to files:

  • tests/integration/api/projects/server.integration.test.ts
  • tests/integration/jobs/log-cleanup-ordering.integration.test.ts
  • src/lib/server/utils/incidents.unit.test.ts
  • tests/fixtures/db.ts
  • tests/integration/db/project.integration.test.ts
  • tests/integration/simple-ingest/logs.integration.test.ts
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to tests/setup.ts : Automatically apply database migrations in integration tests via `tests/setup.ts` file

Applied to files:

  • tests/integration/api/projects/server.integration.test.ts
  • tests/integration/jobs/log-cleanup-ordering.integration.test.ts
  • src/lib/server/utils/incidents.unit.test.ts
  • tests/fixtures/db.ts
  • tests/integration/db/project.integration.test.ts
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to src/lib/server/jobs/**/*.ts : Apply automated log cleanup via background job in `src/lib/server/jobs/log-cleanup.ts`

Applied to files:

  • tests/integration/jobs/log-cleanup-ordering.integration.test.ts
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to src/routes/(app)/**/*.svelte : Use authenticated route structure with `(app)/` directory for protected routes

Applied to files:

  • src/routes/(app)/+page.svelte
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to src/lib/components/ui/**/*.svelte : Organize Svelte components in `src/lib/components/ui/` directory and import shadcn components from there

Applied to files:

  • src/routes/(app)/+page.svelte
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Use shadcn-svelte components + Tailwind CSS v4 for UI development

Applied to files:

  • src/routes/(app)/+page.svelte
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to tests/e2e/**/*.spec.ts : Place E2E tests in `tests/e2e/` directory using Playwright with Chromium and Firefox browsers

Applied to files:

  • tests/e2e/project-settings.spec.ts
  • .github/workflows/ci.yml
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to playwright.config.ts : Use `playwright.config.ts` configuration for E2E tests with Chromium and Firefox browsers

Applied to files:

  • tests/e2e/project-settings.spec.ts
  • .github/workflows/ci.yml
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Configure Drizzle ORM with PostgreSQL 18 schema defined in `src/lib/server/db/schema.ts`

Applied to files:

  • src/lib/server/db/schema.ts
  • src/routes/api/projects/[id]/+server.ts
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to {drizzle.config.ts,drizzle/**/*.sql} : Configure database migrations in `drizzle.config.ts` and store migration files in `drizzle/` folder

Applied to files:

  • src/lib/server/db/schema.ts
🪛 zizmor (1.25.2)
.github/workflows/release.yml

[error] 46-46: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)


[error] 86-86: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)


[error] 123-123: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)


[error] 180-180: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)


[error] 258-258: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

.github/workflows/ci.yml

[error] 43-43: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)


[error] 83-83: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)


[error] 126-126: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)


[error] 178-178: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)


[error] 259-259: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)


[error] 317-317: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🔇 Additional comments (39)
.github/workflows/ci.yml (1)

38-40: LGTM!

Also applies to: 78-80, 121-123, 173-175, 229-233, 254-256, 312-314, 337-339, 377-379

.github/workflows/opencode.yml (1)

27-32: LGTM!

.github/workflows/release.yml (1)

41-43: LGTM!

Also applies to: 81-83, 118-120, 175-177, 253-255, 299-301, 442-444

src/lib/server/db/schema.ts (1)

21-23: LGTM!

tests/integration/api/projects/server.integration.test.ts (1)

10-10: LGTM!

Also applies to: 290-290

tests/integration/db/project.integration.test.ts (1)

43-43: LGTM!

Also applies to: 51-51, 118-123, 127-127

tests/integration/jobs/log-cleanup-ordering.integration.test.ts (1)

1-10: LGTM!

Also applies to: 20-87

.github/workflows/sdk-go.yml (1)

42-44: LGTM!

Also applies to: 71-74, 93-95

.github/workflows/sdk-typescript.yml (1)

40-43: LGTM!

Also applies to: 68-71, 93-96, 118-121, 158-161

drizzle/0009_hash_only_api_keys.sql (1)

1-12: LGTM!

entrypoint.sh (1)

14-19: LGTM!

tests/e2e/project-settings.spec.ts (1)

215-221: LGTM!

Also applies to: 223-235, 237-258, 277-279, 458-460, 469-470, 478-479, 481-492, 502-503

tests/integration/api/projects/authorization.integration.test.ts (1)

9-9: LGTM!

Also applies to: 22-22, 301-304, 320-320, 325-328

tests/integration/api/projects/project-detail.integration.test.ts (1)

10-10: LGTM!

Also applies to: 14-14, 167-167, 309-309, 332-332, 433-433, 455-455, 459-459

sdks/go/logwell/client.go (1)

207-208: LGTM!

Also applies to: 234-235, 237-274, 374-386, 399-407, 409-415, 431-438

sdks/python/src/logwell/config.py (1)

106-110: LGTM!

Also applies to: 121-123

src/lib/components/api-key-reveal-modal.svelte (1)

1-21: LGTM!

Also applies to: 28-37, 41-99

src/lib/components/log-table.svelte (1)

18-22: LGTM!

Also applies to: 117-117, 212-212

src/lib/server/utils/incidents.ts (1)

238-240: LGTM!

src/lib/server/utils/incidents.unit.test.ts (1)

8-9: LGTM!

Also applies to: 34-35, 74-77

src/routes/v1/logs/+server.ts (1)

83-86: LGTM!

Also applies to: 134-135

sdks/typescript/src/queue.ts (1)

89-137: LGTM!

src/lib/server/utils/api-key.ts (3)

65-143: LGTM!


155-218: LGTM!


229-252: LGTM!

src/routes/(app)/+page.svelte (1)

25-40: LGTM!

Also applies to: 69-73, 137-139

src/routes/(app)/projects/[id]/settings/+page.svelte (3)

38-41: LGTM!

Also applies to: 90-92


213-232: LGTM!


345-391: LGTM!

src/routes/api/projects/[id]/+server.ts (3)

40-84: LGTM!


112-207: LGTM!


225-248: LGTM!

src/routes/api/projects/[id]/regenerate/+server.ts (1)

25-57: LGTM!

tests/integration/simple-ingest/logs.integration.test.ts (6)

11-11: LGTM!


86-177: LGTM!


180-296: LGTM!


299-428: LGTM!


430-573: LGTM!


575-609: LGTM!

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/sdk-typescript.yml Outdated
Comment thread src/lib/components/api-key-reveal-modal.svelte
Comment thread tests/integration/jobs/log-cleanup-ordering.integration.test.ts
Divkix added 3 commits June 8, 2026 01:42
- dashboard 'create project and show in list': handle the new one-time API key
  reveal modal that appears after UI creation
- log-stream 'API key on settings page': key is no longer shown on load (hashed,
  shown once); assert it's absent and the regenerate button is present
- ci: Disable bun cache for untrusted PRs and releases to prevent cache poisoning
- ci: Pin oven-sh/setup-bun to 1.2.15 in SDK workflow
- fix(ui): Prevent handleKeyDown from executing if API key modal is closed
- test: Fix unclosed test DB handles in log-cleanup ordering
- fix(sdk): Await in-flight queue flush during graceful shutdown
- test: Use shared hashApiKey utility in DB fixtures

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/e2e/dashboard.spec.ts (1)

225-229: ⚡ Quick win

Strengthen the API key assertion to validate full format, not just prefix.

toContainText('lw_') can pass malformed values. Assert the complete key pattern (lw_ + 32 allowed chars) so this test catches key-shape regressions.

Suggested test assertion update
-    await expect(page.getByTestId('api-key-reveal-value')).toContainText('lw_');
+    await expect(page.getByTestId('api-key-reveal-value')).toHaveText(/^lw_[A-Za-z0-9_-]{32}$/);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/dashboard.spec.ts` around lines 225 - 229, The test currently only
checks the API key prefix; update the assertion on
getByTestId('api-key-reveal-value') to validate the full key shape by asserting
it starts with "lw_" followed by exactly 32 alphanumeric characters (A–Z, a–z,
0–9) instead of using toContainText('lw_'), leaving the surrounding visibility
checks (api-key-reveal-content and api-key-reveal-close) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/e2e/dashboard.spec.ts`:
- Around line 225-229: The test currently only checks the API key prefix; update
the assertion on getByTestId('api-key-reveal-value') to validate the full key
shape by asserting it starts with "lw_" followed by exactly 32 alphanumeric
characters (A–Z, a–z, 0–9) instead of using toContainText('lw_'), leaving the
surrounding visibility checks (api-key-reveal-content and api-key-reveal-close)
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0f59bd15-ee43-41c8-96b3-bea5d4f4bdeb

📥 Commits

Reviewing files that changed from the base of the PR and between bd3470d and ab6ca82.

📒 Files selected for processing (9)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .github/workflows/sdk-typescript.yml
  • sdks/typescript/src/queue.ts
  • src/lib/components/api-key-reveal-modal.svelte
  • tests/e2e/dashboard.spec.ts
  • tests/e2e/log-stream.spec.ts
  • tests/fixtures/db.ts
  • tests/integration/jobs/log-cleanup-ordering.integration.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/integration/jobs/log-cleanup-ordering.integration.test.ts
  • src/lib/components/api-key-reveal-modal.svelte
  • .github/workflows/sdk-typescript.yml
  • .github/workflows/release.yml
  • sdks/typescript/src/queue.ts
  • .github/workflows/ci.yml
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Docker Build
  • GitHub Check: Unit Tests
  • GitHub Check: Integration Tests
  • GitHub Check: E2E Tests
🧰 Additional context used
📓 Path-based instructions (3)
tests/e2e/**/*.spec.ts

📄 CodeRabbit inference engine (AGENTS.md)

Place E2E tests in tests/e2e/ directory using Playwright with Chromium and Firefox browsers

Files:

  • tests/e2e/log-stream.spec.ts
  • tests/e2e/dashboard.spec.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Enable import organization in Biome configuration for consistent module import ordering

Files:

  • tests/e2e/log-stream.spec.ts
  • tests/e2e/dashboard.spec.ts
  • tests/fixtures/db.ts
tests/fixtures/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Store test fixtures in tests/fixtures/ directory for reuse across test suites

Files:

  • tests/fixtures/db.ts
🧠 Learnings (2)
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to tests/setup.ts : Automatically apply database migrations in integration tests via `tests/setup.ts` file

Applied to files:

  • tests/fixtures/db.ts
📚 Learning: 2026-05-14T23:12:26.023Z
Learnt from: CR
Repo: Divkix/Logwell PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T23:12:26.023Z
Learning: Applies to tests/integration/**/*.test.ts : Place integration tests in `tests/integration/` directory using PGlite in-memory database via `electric-sql/pglite`

Applied to files:

  • tests/fixtures/db.ts
🔇 Additional comments (2)
tests/e2e/log-stream.spec.ts (1)

536-548: LGTM!

tests/fixtures/db.ts (1)

4-4: LGTM!

Also applies to: 78-78, 152-152

@gitguardian

gitguardian Bot commented Jun 8, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
24037119 Triggered Generic Password 1c52733 src/hooks.server.test.ts View secret
24037119 Triggered Generic Password 1c52733 src/hooks.server.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Divkix added 8 commits June 8, 2026 02:58
- Extract TimeRange type to src/lib/utils/time-range.ts to fix TS2614 import from .svelte
- Delete empty src/lib/index.ts (no-empty-file)
- Add exhaustive default throw in getTimeBucketConfig (TS2366)
- Cast unknown in template literals (restrict-template-expressions, no-base-to-string)
- Add void to fire-and-forget runCleanupWithGuard call (no-floating-promises)
- Await rerender() in component tests; remove await from .click() (await-thenable)
- Fix unknown | null redundancy in otlp.ts and toast.ts (no-redundant-type-constituents)
- Type-assert better-auth result.error access in seed-admin (TS2339)
- Include tests/ in SDK tsconfig and disable noUncheckedIndexedAccess (TS2532)
- Add definite assignment to resolveFirst in queue.unit.test (TS2454)
- Add scripts/tsconfig.json with node types to fix process not found (TS2591)
- fix TIME_RANGES/TIME_RANGE_LABELS missing from time-range-picker instance script
- fix build/dev/preview scripts: bun --bun run vite -> vp (Script not found 'vite')
- fix Dockerfile build command to use bun run build (delegates to vp build)
- add sdks/** to vite.config.ts ignorePatterns so root vp check skips SDK files
- add bun run prepare before svelte-check in ci.yml lint job (.svelte-kit/tsconfig.json missing)
- fix sdks/typescript test scripts: bare vitest -> vp test run (command not found)
- parallelize integration tests into 3 shards via GitHub Actions matrix strategy
Four CI job categories had been red since the PR opened:

- Docker Build: the build-time DATABASE_URL/BETTER_AUTH_SECRET in the
  Dockerfile had been replaced with literal asterisks, so postgres-js
  could not parse the connection string during `bun run build`
  (TypeError: cannot be parsed as a URL). Restore valid throwaway
  placeholders.

- Lint & Type Check (svelte-check): time-range-picker.svelte used the
  `TimeRange` type in its instance script without importing it — the
  module-context `export type ... from` re-export does not create a local
  binding. Import the type.

- SDK TypeScript / Lint & Type Check (vp check): oxlint/tsgolint parses
  every tsconfig in the repo tree, including the root SvelteKit
  tsconfig.json which `extends ./.svelte-kit/tsconfig.json`. That file is
  generated by `svelte-kit sync`, which never runs in the SDK-only job, so
  the root tsconfig failed to parse. Add a minimal empty stub in the SDK
  lint job so it resolves (contributes no files to the SDK check).

- E2E Tests: useLogStream/useIncidentStream had switched their internal
  connection state to $state. Since connect()/disconnect() are called from
  a component $effect and read those signals in their guards, the effect
  took a reactive dependency on state it also wrote, causing an infinite
  effect_update_depth_exceeded loop that aborted hydration on
  /projects/[id] — every click-driven interaction silently failed. Revert
  the internals to plain variables; connection state is still surfaced to
  the UI via the onConnectionChange callback.
…dration fix

With the /projects/[id] hydration loop fixed, E2E shards ran to completion and
exposed two latent test issues (previously masked by the shard being cancelled):

- dashboard.spec: the one-time API key assertion used /^lw_[a-zA-Z0-9]{32}$/, but
  keys are `lw_` + nanoid(32) whose URL-safe alphabet includes `-` and `_`. The
  test passed only when the random key happened to contain neither (~36%). Match
  the app's own API_KEY_REGEX and the other specs: /^lw_[A-Za-z0-9_-]{32}$/.

- stats.spec: "update timeseries chart when time range changes" registered
  waitForResponse AFTER clicking, so on CI's fast localhost the /stats/timeseries
  response could land before the listener attached -> 30s timeout. Register the
  listener before the click (matching the test's initial-load pattern).
…ss, rate limiting)

- migrations: bump 0008 journal `when` above 0007 so drizzle-kit no longer
  skips it on existing DBs (would otherwise abort on 0009 and crash-loop);
  add guard test for strictly-increasing journal timestamps
- python sdk: chunk flush by batch_size to avoid >100 infinite re-queue loop;
  await in-flight flush on shutdown; reject batch_size > 100 in config
- ts sdk: only set fetch keepalive under 64 KiB so large batches still send
- auth: enforce LOGIN_RPM brute-force protection on POST /api/auth/sign-in
- cleanup: remove dead serializeToCsv + orphaned ExportableLog type
- ui: clear new-log highlight timers on unmount only, not on live-mode toggle
@Divkix

Divkix commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review
--dir src/lib/server
--dir src/lib/shared
--dir src/lib/hooks
--dir src/lib/stores
--dir src/lib/utils
--dir src/routes
--dir drizzle
--dir tests/integration
--dir tests/e2e

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

@Divkix I’ll run a full review focused on the requested paths:

  • src/lib/server
  • src/lib/shared
  • src/lib/hooks
  • src/lib/stores
  • src/lib/utils
  • src/routes
  • drizzle
  • tests/integration
  • tests/e2e
✅ Action performed

Full review finished.

@Divkix Divkix merged commit 031a88b into main Jun 8, 2026
41 checks passed
@Divkix Divkix deleted the audit-improvements branch June 8, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant