Conversation
Update all references from feedsmith.dev to feed.tuvix.app in the URL-based subscription documentation. This corrects the domain used in example URLs, bookmarklets, and integration examples. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add mention of the Tuvix Tricorder Extension for Chrome and Firefox in the Hosted section of the README. This helps users discover the companion extension for easy RSS feed subscription. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
docs: add browser extension section to README
docs: fix incorrect subscription URLs in url-subscribe documentation
Add extractHeaders() utility function to normalize request header extraction across authentication flows. Handles both Headers object and plain object formats, reducing code duplication by ~100 lines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Fix silent email failures by adding comprehensive breadcrumbs and proper error checking throughout email sending flows. - Add breadcrumbs at email service level (attempt, API call, result) - Change Better Auth callbacks from .catch() to .then() to check emailResult.success (email service returns errors, doesn't throw) - Add breadcrumbs for verification, welcome, and password reset emails - Add Sentry capture for settings check failures - Ensures all email failures are tracked for debugging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add complete tracking and security logging throughout authentication endpoints to prevent silent failures and enable monitoring. Login flow enhancements: - Wrap in Sentry span for performance tracking - Add breadcrumbs for login attempts and results - Add security audit logging for successful and failed logins - Add metrics for monitoring login success rates and duration - Use extractHeaders() utility for consistency Password operation security: - Add security audit logging to password change endpoint - Query verification token before reset to capture userId - Add security audit logging to password reset completion - Non-blocking audit logging to prevent operation failures All audit logging wrapped in try/catch to ensure authentication operations succeed even if logging fails. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Wrap verification and welcome email operations in Sentry spans to track success/failure even though emails are sent in a fire-and-forget pattern. - Add span tracking for verification email with status codes - Add span tracking for welcome email with status codes - Track email success/failure as span attributes - Maintain existing breadcrumbs for debugging - Preserve fire-and-forget behavior (no await on email send) This provides visibility into email delivery without blocking user registration/verification flows. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive Sentry span tracking around batch database operations to monitor performance and catch failures. - Wrap mark articles read/unread batch in Sentry span - Wrap mark all articles read batch in Sentry span - Track batch size, operation type, and user ID as attributes - Capture exceptions with full context (batch size, user, article count) - Set appropriate span status codes for success/failure Batch operations are critical for user experience - this ensures we're alerted to failures and can identify performance patterns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add Sentry span tracking for OpenGraph image extraction with metrics emission for pattern analysis. - Wrap OG image fetch in Sentry span with domain tracking - Track success (found/not found) and failures separately - Emit metrics counters for error patterns (by domain and error type) - Set appropriate span status for found/not found/error cases - Avoid spamming Sentry with every failure (use metrics instead) This allows us to identify problematic domains and error patterns without creating excessive noise in Sentry. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Implement automatic retry for transient feed fetch failures and add full transaction monitoring for OPML import operations. Subscription Creation & Preview Enhancements: - Add automatic retry logic for HTTP 502, 503, 504, 429 errors - Implement exponential backoff (1s, 2s delays) with max 2 retries - Track retry attempts in Sentry breadcrumbs and error context - Enhanced error tagging with domain and HTTP status codes - Track attempt count and last status code for debugging OPML Import Transaction Monitoring: - Wrap entire import in Sentry transaction span - Create individual spans for each feed import - Track feed-level attributes (URL, title, domain, filters, categories) - Calculate and report success rate as span attribute - Proper error boundaries with per-feed exception capture - Handle limit reached and already subscribed cases gracefully - Fix loop control flow to work within Sentry span callbacks Benefits: - Improved resilience against transient failures (network, server) - Clear visibility into which feeds fail and why during bulk imports - Domain-based error pattern analysis for subscription issues - Success rate tracking for OPML import operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Fix critical bugs identified in code review: 1. Security Audit Logging (HIGH): - Fix incorrect action type for failed password resets - Changed "password_reset_success" to "password_reset_failed" in error handler - Added missing "password_reset_failed" to SecurityAction type 2. Retry Logic Separation (HIGH): - Separate fetch errors from parse errors in retry logic - Parse errors now throw immediately without retry - Only HTTP fetch errors (502, 503, 504, 429) are retried - Improves error classification: "fetch_error" vs "parse_error" - Applied to both create and preview endpoints 3. Header Extraction (MEDIUM): - Fix String(undefined) converting undefined to "undefined" string - Preserve undefined values correctly in header extraction - Prevents confusion where missing headers appear as "undefined" 4. Code Cleanup (LOW): - Remove unused lastError assignments in retry loops - Assignments were made but value never used before continue All changes verified with 819 passing tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add explicit paths for packages/api/wrangler.toml files since root-level patterns don't match subdirectories 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Admin dashboard was missing emailVerified field, causing unverified users to appear as "active" when they weren't. This adds: - emailVerified field to AdminUserSchema - emailVerified filter option for queries - Metrics tracking for when filter is used Fixes user status discrepancy where unverified users showed as active 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Security audit logging was completely non-functional due to missing SQL default on created_at column. This caused silent failures: Root cause: - security_audit_log.created_at was NOT NULL without SQL DEFAULT - Drizzle's $defaultFn() only works at app level, not in database - logSecurityEvent() didn't provide createdAt value - All insert attempts failed silently (caught and logged to console) - Result: Zero audit logs ever written to production Fix (3-part): 1. Quick fix: logSecurityEvent() now explicitly provides createdAt 2. Schema fix: Changed to SQL DEFAULT using unixepoch() 3. Migration: 0006_fix_security_audit_log_default.sql adds DEFAULT to DB Impact: - Enables tracking of login attempts, password resets, registrations - Critical for security monitoring and compliance - Consistent with other tables using same timestamp pattern 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
While .claude/ directory is gitignored, settings.json should be committed to share team-wide Claude Code configurations like security policies and approved tool permissions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add shared Claude Code configuration with: - Security deny rules for .env, secrets, production operations - Broader allow patterns for common workflows - Git co-authoring enabled - MCP server integrations (Sentry, Better Auth, Shadcn) Establishes team-wide safety guardrails and tool permissions for AI-assisted development 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Improvements from code review: - Extract getRequestMetadata() helper to reduce header extraction duplication - Consolidate security module imports in auth router - Extract retry constants (MAX_RETRIES, RETRY_DELAY_MS, TRANSIENT_STATUS_CODES) to module top - Add clarifying comment for createdAt defense-in-depth pattern No functional changes, just improved maintainability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Import logSecurityEvent and getRequestMetadata in login handler - Add emailVerified field to admin listUsers and getUser responses - Fixes type errors from refactoring 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Dynamic imports lose type information, causing ESLint to treat destructured values as 'error' types. Added explicit type annotations to resolve "unsafe assignment" and "unsafe call" linting errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Type assertions (as) are more effective than type annotations (:) for dynamic imports, as they assert the return type of the function call rather than just the destructured variables. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Remove dynamic imports of security module to fix type inference - Add explicit type annotations to destructured variables - Use top-level import instead of await import() pattern This resolves ESLint errors where dynamic imports lose type information, causing getRequestMetadata to be treated as 'error' type. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add top-level import for Sentry module - Add nullish coalescing for optional olderThanDays attribute Resolves ESLint errors from missing Sentry import and TypeScript errors from passing undefined to Sentry span attributes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add nullish coalescing for extractDomain() calls - Prevents TypeScript errors when domain extraction returns null Sentry span attributes require string | number | boolean types, not null or undefined. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ements - Add --> statement-breakpoint markers for Drizzle compatibility - Prevents "more than one statement" error in test migrations - Maintains same logic, just properly formatted for migration runner This fixes test failures caused by better-sqlite3 rejecting multi-statement SQL strings. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Security audit logging: - Remove redundant createdAt field setting in application code - Rely solely on SQL DEFAULT from database schema Subscription error handling: - Add TRPCError rethrow guards in catch blocks - Prevents parse errors from being incorrectly retried as fetch errors - Ensures parse errors exit immediately without retry logic All three issues identified by Copilot AI have been resolved. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Merge executeBatch import with existing @/db/utils imports - Group @/db/* imports together for better organization - Remove duplicate import statement 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Observability/signup
Fixes #75 ## Problem Docker Compose builds were failing with "ERR_PNPM_OUTDATED_LOCKFILE" because the build context was set to individual package directories (./packages/api, ./packages/app), but pnpm workspaces require the monorepo root context to access the shared pnpm-lock.yaml. The individual package lockfiles were outdated and shouldn't exist in a pnpm workspace setup. ## Solution 1. Changed Docker build context from individual packages to monorepo root (.) 2. Updated Dockerfiles to copy workspace files (pnpm-workspace.yaml, root pnpm-lock.yaml) 3. Updated Dockerfiles to copy all needed package.json files 4. Removed outdated individual package lockfiles 5. Added packages/*/pnpm-lock.yaml to .gitignore 6. Updated deployment documentation ## Changes - docker-compose.yml: Changed context from ./packages/* to . (monorepo root) - packages/api/Dockerfile: Copy workspace files and all needed packages - packages/app/Dockerfile: Copy workspace files and all needed packages - Removed packages/api/pnpm-lock.yaml (outdated) - Removed packages/app/pnpm-lock.yaml (outdated) - .gitignore: Prevent individual package lockfiles - docs/deployment.md: Updated to reflect new build context ## Testing ✅ Tested with `docker compose build --no-cache` ✅ Both API and app containers build successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Fixes #75 ## Root Causes Identified 1. **Lockfile mismatch**: Docker build context was set to individual packages but pnpm workspaces require monorepo root context 2. **ESM import issues**: TypeScript compilation without bundling resulted in missing `.js` extensions in imports (Node ESM requirement) 3. **Double-migration bug**: When tsup bundled migrate-local.ts into node.ts, the CLI check (import.meta.url) executed twice, causing migrations to run twice and server to hang ## Solutions Implemented ### 1. Monorepo Context (from previous commit 23e386b) - Changed docker-compose.yml context from `./packages/*` to `.` (root) - Updated Dockerfiles to copy workspace files - Removed outdated individual package lockfiles - Added packages/*/pnpm-lock.yaml to .gitignore ### 2. Add tsup Bundler - Added tsup as dev dependency for proper ESM bundling - Created tsup.config.ts to bundle node.ts and migrate-local.ts - tsup handles ESM imports correctly (no .js extension issues) - Optimized bundle: 525KB for node.ts entry point - Updated package.json build script to use tsup ### 3. Fix Double-Migration Bug - Added check in migrate-local.ts to prevent CLI code from running when bundled into dist/entries/node.js - CLI check now excludes execution when argv[1] contains "dist/entries" - Migrations now run exactly once during server startup ### 4. Update Dockerfile CMD - Changed from running separate migration + server to just server - Migrations now run automatically inside node.ts (as designed) - Removed redundant migration command from CMD ## Testing ✅ Local build: `pnpm --filter @tuvixrss/api build` succeeds ✅ Local run: Server starts, migrations run once, cron initializes ✅ Docker build: `docker compose build` succeeds ✅ Docker run: Containers healthy, API responds on :3001 ✅ Health check: `curl http://localhost:3001/health` returns OK ## Changes - packages/api/Dockerfile: Use tsup-bundled code, simplified CMD - packages/api/package.json: Add tsup dependency, update build script - packages/api/tsup.config.ts: Configure bundling for node.ts + migrate-local.ts - packages/api/src/db/migrate-local.ts: Fix double-execution bug - pnpm-lock.yaml: Add tsup dependencies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Security improvements: - Run containers as non-root users (uid 1001) - API runs as 'nodejs' user - App runs as 'nginx-app' user Build optimization: - Add .dockerignore files (root and API package) - Reduce build context by excluding unnecessary files - Build tricorder before API in multi-stage build Health checks: - Add explicit health check to app Dockerfile - Consistent health check configuration across services Image sizes: - API: 553MB (increased from 357MB due to security layer overhead) - App: 60.9MB (optimized from 57.1MB with health checks) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Critical Fix: - Create non-root user BEFORE copying files (was causing 196MB layer) - Use --chown flag during COPY operations instead of chown after - This prevents Docker copy-on-write from duplicating the entire /app directory Results: - API image: 357MB (down from 553MB with previous optimization) - User creation layer: 3.22kB (down from 196MB) - All functionality verified working Technical Details: - The issue was that running `chown -R nodejs:nodejs /app` after copying files created a new 196MB layer because Docker's copy-on-write system duplicates all files when permissions change - By creating the user first and using COPY --chown, we set permissions during the copy operation, avoiding the duplication Security maintained: - Still runs as non-root user (nodejs:1001) - Health checks working - Containers start successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Fix/docker compose
Add comprehensive CI/CD testing for Docker Compose deployment: GitHub Actions Workflow (.github/workflows/docker-test.yml): - Build validation for both API and App images - Image size enforcement (API < 400MB, App < 100MB) - Health check verification with 60s timeout - Endpoint testing (API and App health checks) - Security validation (non-root user verification) - Database migration verification - Smoke tests for basic functionality - Detailed logging and cleanup on failure Test Configuration (docker-compose.test.yml): - Faster health check intervals for CI (5s vs 30s) - Test-specific environment variables - Extended retries for reliability Local Testing Script (scripts/test-docker.sh): - Run the same CI tests locally before pushing - Colorized output for easy debugging - Automatic cleanup on exit/interrupt - Shows container stats and leaves services running Package Scripts: - `pnpm docker:test` - Run local Docker tests - `pnpm docker:test:ci` - Run with CI configuration Triggers: - Pull requests affecting Docker-related files - Pushes to main or dev branches Expected CI runtime: 3-5 minutes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add script to check for outdated GitHub Actions locally without waiting for Dependabot's monthly schedule. Usage: ./scripts/check-gha-versions.sh 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Update GitHub Actions dependencies: - actions/checkout@v4 → @v6 - actions/github-script@v7 → @v8 - actions/setup-node@v5 → @v6 - actions/upload-artifact@v4 → @v5 Major version pinning allows automatic security patches while preventing breaking changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The API container needs time to: - Run database migrations - Initialize Sentry - Start cron jobs - Boot the Hono server Increased from 5s to 30s to prevent false health check failures in CI environments. Also fixed test-docker.sh to work on macOS (no timeout command). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The docker-compose.yml health check configuration overrides the Dockerfile HEALTHCHECK. Updated both to use consistent timing: - API: 30s start period (needs time for migrations + Sentry + cron) - App: 10s start period (nginx starts faster) This fixes the CI failure where docker compose up -d was exiting because the API was marked unhealthy before fully starting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Changes: - Start API and App containers separately (not docker compose up -d) - Show container status and logs immediately after starting - Poll health status every 2s with timestamp logging - Show API logs every 10s while waiting - Increased API wait time to 90s (was 60s implicit) - Show full logs on failure for better debugging This allows us to see exactly what's happening during startup in CI and diagnose why health checks are failing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The real issue was a permissions problem with the SQLite database: - Container runs as uid 1001 (nodejs user) - Volume mount ./data:/app/data didn't exist in CI - When Docker creates the mount point, it's owned by root - nodejs user can't write to /app/data/tuvix.db Solution: Create ./data directory with 777 permissions before starting containers, allowing uid 1001 to write the database file. This fixes the "SqliteError: unable to open database file" crash that was causing the health check failures. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Changed health check interval from 30s to 5s for both containers. This allows health status to update quickly instead of waiting 30s between each check. With the old config: - start_period: 10s - interval: 30s - retries: 3 - = could take 100+ seconds to become healthy With new config: - start_period: 10-30s - interval: 5s - retries: 3 - = becomes healthy in 10-40 seconds Also increased App wait timeout to 120s to be safe. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Change nginx to listen on port 8080 instead of 80 (non-root compatible) - Update port mapping from 5173:80 to 5173:8080 - Fix health checks to use explicit IPv4 (127.0.0.1) instead of localhost - Move non-root user creation before nginx config for clarity - Add manual health check test in CI workflow This fixes the app container health check failures while maintaining security by running as non-root user (uid 1001). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…pose - Change hardcoded DATABASE_PATH to use environment variable with fallback - Allows CI to use /app/data/test.db while local dev uses /app/data/tuvix.db - Fixes CI database verification step that was looking for test.db This resolves the "Database file not found" error in CI workflow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request introduces comprehensive Docker build and testing infrastructure along with enhanced observability, retry logic, and security improvements. The changes focus on production-readiness by adding Docker health checks, non-root user support, automated testing workflows, and improved error tracking through Sentry.
Key Changes:
- Docker infrastructure with automated testing, health checks, and non-root security
- Database retry logic for transient HTTP failures in subscription endpoints
- Enhanced Sentry observability with breadcrumbs, spans, and metrics across auth, email, and RSS operations
- Build system improvements using tsup for better ESM bundling
Reviewed changes
Copilot reviewed 33 out of 37 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
scripts/test-docker.sh |
Comprehensive Docker testing script with health checks and validation |
scripts/check-gha-versions.sh |
GitHub Actions version checking utility |
packages/api/Dockerfile |
Multi-stage build with tsup bundling, non-root user, and proper workspace structure |
packages/app/Dockerfile |
Nginx-based app container with non-root user and port 8080 |
docker-compose.yml |
Updated build contexts to monorepo root with adjusted health check intervals |
docker-compose.test.yml |
CI-specific Docker Compose configuration |
packages/api/tsup.config.ts |
New bundler config for ESM output with workspace package resolution |
packages/api/src/routers/subscriptions.ts |
Retry logic for transient HTTP errors with exponential backoff and Sentry spans |
packages/api/src/routers/auth.ts |
Enhanced audit logging and Sentry observability for auth flows |
packages/api/src/services/email.ts |
Breadcrumb tracking for email operations |
packages/api/src/auth/better-auth.ts |
Fire-and-forget email tracking with Sentry spans |
packages/api/src/db/schema.ts |
SQL DEFAULT for timestamp instead of runtime function |
.github/workflows/docker-test.yml |
Automated Docker build and integration testing workflow |
docs/features/url-subscribe.md |
URL updated from feedsmith.dev to feed.tuvix.app |
README.md |
Browser extension announcement added |
CLAUDE.md |
New AI assistant guidelines document |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request updates documentation to reflect the new Tuvix subscription URL and introduces the browser extension for easier feed discovery. The changes ensure users and developers reference the correct service endpoint and highlight new integration options.
Documentation updates for subscription URL:
docs/features/url-subscribe.mdto use the newhttps://feed.tuvix.app/app/subscriptionsURL instead of the oldfeedsmith.devendpoint. [1] [2] [3] [4]New browser extension announcement:
README.mdintroducing the Tuvix Tricorder Extension for Chrome and Firefox, with a link to its repository for easy RSS feed discovery.