-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Fix race condition in concurrent crawling with unique source IDs #472
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
47edbb1
bc11a0c
40abaf9
56220bd
b9d52fb
5e603ea
76bf0f0
698e3b9
f5de76d
353264d
52187e2
fd9209c
a7da288
49f9280
a8b5a65
3eda01e
f65c4ae
75958f4
7dca34b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,60 @@ | ||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||
| import { AlertTriangle, ExternalLink } from 'lucide-react'; | ||||||||||||||||||||||||
| import { Card } from './Card'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| interface MigrationBannerProps { | ||||||||||||||||||||||||
| message: string; | ||||||||||||||||||||||||
| onDismiss?: () => void; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export const MigrationBanner: React.FC<MigrationBannerProps> = ({ | ||||||||||||||||||||||||
| message, | ||||||||||||||||||||||||
| onDismiss | ||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||
|
Comment on lines
+10
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Plumb the new instructions prop through the component Wire the prop so the rendering logic can use it. export const MigrationBanner: React.FC<MigrationBannerProps> = ({
message,
- onDismiss
+ instructions,
+ onDismiss
}) => {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||
| <Card className="bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-800 mb-6"> | ||||||||||||||||||||||||
| <div className="flex items-start gap-3 p-4"> | ||||||||||||||||||||||||
| <AlertTriangle className="w-6 h-6 text-red-500 flex-shrink-0 mt-0.5" /> | ||||||||||||||||||||||||
| <div className="flex-1"> | ||||||||||||||||||||||||
| <h3 className="text-lg font-semibold text-red-800 dark:text-red-300 mb-2"> | ||||||||||||||||||||||||
| Database Migration Required | ||||||||||||||||||||||||
| </h3> | ||||||||||||||||||||||||
| <p className="text-red-700 dark:text-red-400 mb-3"> | ||||||||||||||||||||||||
| {message} | ||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||
| <div className="bg-red-100 dark:bg-red-900/40 border border-red-200 dark:border-red-800 rounded-lg p-3 mb-3"> | ||||||||||||||||||||||||
| <p className="text-sm font-medium text-red-800 dark:text-red-300 mb-2"> | ||||||||||||||||||||||||
| Follow these steps: | ||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||
| <ol className="text-sm text-red-700 dark:text-red-400 space-y-1 list-decimal list-inside"> | ||||||||||||||||||||||||
| <li>Open your Supabase project dashboard</li> | ||||||||||||||||||||||||
| <li>Navigate to the SQL Editor</li> | ||||||||||||||||||||||||
| <li>Copy and run the migration script from: <code className="bg-red-200 dark:bg-red-800 px-1 rounded">migration/add_source_url_display_name.sql</code></li> | ||||||||||||||||||||||||
| <li>Restart Docker containers: <code className="bg-red-200 dark:bg-red-800 px-1 rounded">docker compose down && docker compose up --build -d</code></li> | ||||||||||||||||||||||||
| <li>If you used a profile, add it: <code className="bg-red-200 dark:bg-red-800 px-1 rounded">--profile full</code></li> | ||||||||||||||||||||||||
| </ol> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
|
Comment on lines
+25
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify backend contract exposes instructions Before wiring this, confirm the health endpoint or migration status hook surfaces instructions (string or list). If not, add it there. 🏁 Script executed: #!/bin/bash
# Verify whether the UI hook and backend expose `instructions`.
echo "Searching UI for migration status and instructions…"
rg -n -C3 'useMigrationStatus|migration.*required|instructions' archon-ui-main/src || true
echo
echo "Searching backend for /api/health or migration instructions…"
rg -n -C3 -S 'api/health|/health|migration.*instruction|migrationRequired|instructions' --type=py || trueLength of output: 15320 Surface backend-provided migration instructions in the UI The backend’s • In • In • In This will ensure the UI surfaces the exact steps defined by the backend, keeping both sides in sync and avoiding duplicated hard-coded instructions. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| <div className="flex items-center gap-3"> | ||||||||||||||||||||||||
| <a | ||||||||||||||||||||||||
| href="https://supabase.com/dashboard" | ||||||||||||||||||||||||
| target="_blank" | ||||||||||||||||||||||||
| rel="noopener noreferrer" | ||||||||||||||||||||||||
| className="inline-flex items-center gap-2 bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors" | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| <ExternalLink className="w-4 h-4" /> | ||||||||||||||||||||||||
| Open Supabase Dashboard | ||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||
| {onDismiss && ( | ||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||
| onClick={onDismiss} | ||||||||||||||||||||||||
| className="text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-200 text-sm font-medium" | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| Dismiss (temporarily) | ||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| </Card> | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { useState, useEffect } from 'react'; | ||
|
|
||
| interface MigrationStatus { | ||
| migrationRequired: boolean; | ||
| message?: string; | ||
| loading: boolean; | ||
| } | ||
|
|
||
| export const useMigrationStatus = (): MigrationStatus => { | ||
| const [status, setStatus] = useState<MigrationStatus>({ | ||
| migrationRequired: false, | ||
| loading: true, | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| const checkMigrationStatus = async () => { | ||
| try { | ||
| const response = await fetch('/api/health'); | ||
| const healthData = await response.json(); | ||
|
|
||
| if (healthData.status === 'migration_required') { | ||
| setStatus({ | ||
| migrationRequired: true, | ||
| message: healthData.message, | ||
| loading: false, | ||
| }); | ||
| } else { | ||
| setStatus({ | ||
| migrationRequired: false, | ||
| loading: false, | ||
| }); | ||
| } | ||
| } catch (error) { | ||
| console.error('Failed to check migration status:', error); | ||
| setStatus({ | ||
| migrationRequired: false, | ||
| loading: false, | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| checkMigrationStatus(); | ||
|
|
||
| // Check periodically (every 30 seconds) to detect when migration is complete | ||
| const interval = setInterval(checkMigrationStatus, 30000); | ||
|
|
||
| return () => clearInterval(interval); | ||
| }, []); | ||
|
|
||
| return status; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| -- ===================================================== | ||
| -- Add source_url and source_display_name columns | ||
| -- ===================================================== | ||
| -- This migration adds two new columns to better identify sources: | ||
| -- - source_url: The original URL that was crawled | ||
| -- - source_display_name: Human-readable name for UI display | ||
| -- | ||
| -- This solves the race condition issue where multiple crawls | ||
| -- to the same domain would conflict by using domain as source_id | ||
| -- ===================================================== | ||
|
|
||
| -- Add new columns to archon_sources table | ||
| ALTER TABLE archon_sources | ||
| ADD COLUMN IF NOT EXISTS source_url TEXT, | ||
| ADD COLUMN IF NOT EXISTS source_display_name TEXT; | ||
|
|
||
| -- Add indexes for the new columns for better query performance | ||
| CREATE INDEX IF NOT EXISTS idx_archon_sources_url ON archon_sources(source_url); | ||
| CREATE INDEX IF NOT EXISTS idx_archon_sources_display_name ON archon_sources(source_display_name); | ||
|
|
||
| -- Add comments to document the new columns | ||
| COMMENT ON COLUMN archon_sources.source_url IS 'The original URL that was crawled to create this source'; | ||
| COMMENT ON COLUMN archon_sources.source_display_name IS 'Human-readable name for UI display (e.g., "GitHub - microsoft/typescript")'; | ||
|
|
||
| -- Backfill existing data | ||
| -- For existing sources, copy source_id to both new fields as a fallback | ||
| UPDATE archon_sources | ||
| SET | ||
| source_url = COALESCE(source_url, source_id), | ||
| source_display_name = COALESCE(source_display_name, source_id) | ||
| WHERE | ||
| source_url IS NULL | ||
| OR source_display_name IS NULL; | ||
|
|
||
| -- Note: source_id will now contain a unique hash instead of domain | ||
| -- This ensures no conflicts when multiple sources from same domain are crawled |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -170,6 +170,8 @@ COMMENT ON TABLE archon_settings IS 'Stores application configuration including | |
| -- Create the sources table | ||
| CREATE TABLE IF NOT EXISTS archon_sources ( | ||
| source_id TEXT PRIMARY KEY, | ||
| source_url TEXT, | ||
| source_display_name TEXT, | ||
| summary TEXT, | ||
|
Comment on lines
+173
to
175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Schema extension is correct; add an updated_at trigger for archon_sources. You already use update_updated_at_column() for other tables; archon_sources lacks that trigger. Today, the app attempts to set updated_at to "now()" (string), which will store a literal string unless a trigger updates it. Add this DDL (outside this hunk): CREATE OR REPLACE TRIGGER update_archon_sources_updated_at
BEFORE UPDATE ON archon_sources
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();Then remove manual "updated_at": "now()" writes in app code (see review in source_management_service.py). 🤖 Prompt for AI Agents |
||
| total_word_count INTEGER DEFAULT 0, | ||
| title TEXT, | ||
|
|
@@ -180,10 +182,15 @@ CREATE TABLE IF NOT EXISTS archon_sources ( | |
|
|
||
| -- Create indexes for better query performance | ||
| CREATE INDEX IF NOT EXISTS idx_archon_sources_title ON archon_sources(title); | ||
| CREATE INDEX IF NOT EXISTS idx_archon_sources_url ON archon_sources(source_url); | ||
| CREATE INDEX IF NOT EXISTS idx_archon_sources_display_name ON archon_sources(source_display_name); | ||
| CREATE INDEX IF NOT EXISTS idx_archon_sources_metadata ON archon_sources USING GIN(metadata); | ||
| CREATE INDEX IF NOT EXISTS idx_archon_sources_knowledge_type ON archon_sources((metadata->>'knowledge_type')); | ||
|
|
||
| -- Add comments to document the new columns | ||
| -- Add comments to document the columns | ||
| COMMENT ON COLUMN archon_sources.source_id IS 'Unique hash identifier for the source (16-char SHA256 hash of URL)'; | ||
| COMMENT ON COLUMN archon_sources.source_url IS 'The original URL that was crawled to create this source'; | ||
| COMMENT ON COLUMN archon_sources.source_display_name IS 'Human-readable name for UI display (e.g., "GitHub - microsoft/typescript")'; | ||
| COMMENT ON COLUMN archon_sources.title IS 'Descriptive title for the source (e.g., "Pydantic AI API Reference")'; | ||
| COMMENT ON COLUMN archon_sources.metadata IS 'JSONB field storing knowledge_type, tags, and other metadata'; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -246,12 +246,27 @@ async def health_check(): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ready": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check for required database schema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| schema_status = await _check_database_schema() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not schema_status["valid"]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "status": "migration_required", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "service": "archon-backend", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "timestamp": datetime.now().isoformat(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ready": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "migration_required": True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "message": schema_status["message"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "migration_instructions": "Open Supabase Dashboard → SQL Editor → Run: migration/add_source_url_display_name.sql", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "schema_valid": False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "status": "healthy", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "service": "archon-backend", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "timestamp": datetime.now().isoformat(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ready": True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "credentials_loaded": True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "schema_valid": True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -262,6 +277,78 @@ async def api_health_check(): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await health_check() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Cache schema check result to avoid repeated database queries | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _schema_check_cache = {"valid": None, "checked_at": 0} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def _check_database_schema(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Check if required database schema exists - only for existing users who need migration.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # If we've already confirmed schema is valid, don't check again | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if _schema_check_cache["valid"] is True: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {"valid": True, "message": "Schema is up to date (cached)"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # If we recently failed, don't spam the database (wait at least 30 seconds) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_time = time.time() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_schema_check_cache["valid"] is False and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_time - _schema_check_cache["checked_at"] < 30): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _schema_check_cache["result"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from .services.client_manager import get_supabase_client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client = get_supabase_client() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Try to query the new columns directly - if they exist, schema is up to date | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test_query = client.table('archon_sources').select('source_url, source_display_name').limit(1).execute() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Cache successful result permanently | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _schema_check_cache["valid"] = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _schema_check_cache["checked_at"] = current_time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {"valid": True, "message": "Schema is up to date"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_msg = str(e).lower() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Log schema check error for debugging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| api_logger.debug(f"Schema check error: {type(e).__name__}: {str(e)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check for specific error types based on PostgreSQL error codes and messages | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check for missing columns first (more specific than table check) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| missing_source_url = 'source_url' in error_msg and ('column' in error_msg or 'does not exist' in error_msg) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| missing_source_display = 'source_display_name' in error_msg and ('column' in error_msg or 'does not exist' in error_msg) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Also check for PostgreSQL error code 42703 (undefined column) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is_column_error = '42703' in error_msg or 'column' in error_msg | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (missing_source_url or missing_source_display) and is_column_error: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "valid": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "message": "Database schema outdated - missing required columns from recent updates" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Cache failed result with timestamp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _schema_check_cache["valid"] = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _schema_check_cache["checked_at"] = current_time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _schema_check_cache["result"] = result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check for table doesn't exist (less specific, only if column check didn't match) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Look for relation/table errors specifically | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ('relation' in error_msg and 'does not exist' in error_msg) or ('table' in error_msg and 'does not exist' in error_msg): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Table doesn't exist - not a migration issue, it's a setup issue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {"valid": True, "message": "Table doesn't exist - handled by startup error"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Other errors don't necessarily mean migration needed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = {"valid": True, "message": f"Schema check inconclusive: {str(e)}"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Don't cache inconclusive results - allow retry | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+319
to
+346
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Treat missing tables as invalid schema and cache failures to avoid repeated exceptions Returning valid=True for a missing table causes /health to report healthy despite a hard setup failure. Also, not caching this failure yields repeated exception work under polling. Return valid=False with targeted instructions and cache for 30s, same as the column-missing case. - if (missing_source_url or missing_source_display) and is_column_error:
+ if (missing_source_url or missing_source_display) and is_column_error:
result = {
- "valid": False,
- "message": "Database schema outdated - missing required columns from recent updates"
+ "valid": False,
+ "message": "Database schema outdated - missing required columns from recent updates",
+ "instructions": "Open Supabase Dashboard → SQL Editor → Run: migration/add_source_url_display_name.sql"
}
# Cache failed result with timestamp
_schema_check_cache["valid"] = False
_schema_check_cache["checked_at"] = current_time
_schema_check_cache["result"] = result
return result
# Check for table doesn't exist (less specific, only if column check didn't match)
# Look for relation/table errors specifically
- if ('relation' in error_msg and 'does not exist' in error_msg) or ('table' in error_msg and 'does not exist' in error_msg):
- # Table doesn't exist - not a migration issue, it's a setup issue
- return {"valid": True, "message": "Table doesn't exist - handled by startup error"}
+ if ('relation' in error_msg and 'does not exist' in error_msg) or ('table' in error_msg and 'does not exist' in error_msg):
+ # Database not initialized - guide user to full setup
+ result = {
+ "valid": False,
+ "message": "Database not initialized - required tables are missing",
+ "instructions": "Open Supabase Dashboard → SQL Editor → Run: migration/complete_setup.sql"
+ }
+ _schema_check_cache["valid"] = False
+ _schema_check_cache["checked_at"] = current_time
+ _schema_check_cache["result"] = result
+ return result
# Other errors don't necessarily mean migration needed
result = {"valid": True, "message": f"Schema check inconclusive: {str(e)}"}
# Don't cache inconclusive results - allow retry
return result📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Export for Socket.IO | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Create Socket.IO app wrapper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # This wraps the FastAPI app with Socket.IO functionality | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| socket_app = create_socketio_app(app) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Extend props to accept optional instructions (string or string[])
Expose backend guidance via an
instructionsprop and keep the API simple.interface MigrationBannerProps { message: string; + instructions?: string | string[]; onDismiss?: () => void; }📝 Committable suggestion
🤖 Prompt for AI Agents