Files

4.2 KiB

Session Loop Bug (Returned)

Issue Description

The session refresh loop bug has returned. After completing setup and the server restarts, the UI flashes/loops rapidly if you're on the dashboard, agents, or settings pages. User must manually logout and log back in to stop the loop.

Previous fix: Commit 7b77641 - "fix: resolve 401 session refresh loop"

  • Added logout() call in Setup.tsx before configuration
  • Cleared auth state on 401 in store
  • Disabled retries in API client

Current Behavior

Steps to reproduce:

  1. Complete setup wizard
  2. Click "Restart Server" button (or restart manually)
  3. Server goes down, Docker components restart
  4. UI automatically redirects from setup to dashboard
  5. BUG: Screen starts flashing/rapid refresh loop
  6. Clicking Logout stops the loop
  7. Logging back in works fine

Suspected Cause

The SetupCompletionChecker component is polling every 3 seconds and has a dependency array issue:

useEffect(() => {
  const checkSetupStatus = async () => { ... }
  checkSetupStatus();
  const interval = setInterval(checkSetupStatus, 3000);
  return () => clearInterval(interval);
}, [wasInSetupMode, location.pathname, navigate]);  // ← Problem here

Issue: wasInSetupMode is in the dependency array. When it changes from false to true to false, it triggers new effect runs, creating multiple overlapping intervals without properly cleaning up the old ones.

During docker restart:

  1. Initial render: creates interval 1
  2. Server goes down: can't fetch health, sets wasInSetupMode
  3. Effect re-runs: interval 1 still running, creates interval 2
  4. Server comes back: detects not in setup mode
  5. Effect re-runs again: interval 1 & 2 still running, creates interval 3
  6. Now 3+ intervals all polling every 3 seconds = rapid flashing

Potential Fix Options

Option 1: Remove wasInSetupMode from dependencies

useEffect(() => {
  let wasInSetup = false;

  const checkSetupStatus = async () => {
    // ... existing logic using wasInSetup local variable
  };

  checkSetupStatus();
  const interval = setInterval(checkSetupStatus, 3000);
  return () => clearInterval(interval);
}, [location.pathname, navigate]);  // Only pathname and navigate

Option 2: Add interval guard

const [intervalId, setIntervalId] = useState<number | null>(null);

useEffect(() => {
  // Clear any existing interval first
  if (intervalId) {
    clearInterval(intervalId);
  }

  const checkSetupStatus = async () => { ... };
  checkSetupStatus();
  const newInterval = setInterval(checkSetupStatus, 3000);
  setIntervalId(newInterval);

  return () => clearInterval(newInterval);
}, [wasInSetupMode, location.pathname, navigate]);

Option 3: Increase polling interval during transitions

const pollingInterval = wasInSetupMode ? 5000 : 3000; // Slower during transition
const interval = setInterval(checkSetupStatus, pollingInterval);

Option 4: Stop polling after successful redirect

if (wasInSetupMode && !currentSetupMode && location.pathname === '/setup') {
  console.log('Setup completed - redirecting to login');
  navigate('/login', { replace: true });
  // Don't set up interval again
  return;
}

Testing

After applying fix:

# 1. Fresh setup
docker-compose down -v --remove-orphans
rm config/.env
docker-compose build --no-cache
cp config/.env.bootstrap.example config/.env
docker-compose up -d

# 2. Complete setup wizard
# 3. Restart server via UI or manually
docker-compose restart server

# 4. Watch browser console for:
#    - Multiple "checking setup status" logs
#    - 401 errors
#    - Rapid API calls to /health endpoint

# 5. Expected: No flashing, clean redirect to login
  • aggregator-web/src/components/SetupCompletionChecker.tsx - Main component
  • aggregator-web/src/lib/store.ts - Auth store with logout()
  • aggregator-web/src/pages/Setup.tsx - Calls logout before configure
  • aggregator-web/src/lib/api.ts - API retry logic

Notes

This bug only manifests during the server restart after setup completion, making it hard to reproduce without a full cycle. The previous fix (commit 7b77641) addressed the 401 loop but didn't fully solve the interval cleanup issue.