- Remove redundant nil checks before len() calls
- Mark unused parameters with underscore
- Convert if/else chains to switch statements for cleaner code
- Add test assertions to resolve unused write warnings in patrol_test.go
- Add lastSaveError, lastSaveTime, onSaveError fields to PatrolRunHistoryStore
- Add GetPersistenceStatus() and SetOnSaveError() methods
- Consistent with findings store and cost store error handling
- Add lastSaveError, lastSaveTime, onSaveError fields to cost.Store
- Add GetPersistenceStatus() method to check persistence health
- Add SetOnSaveError() callback for error notifications
- Rename scheduleSave to scheduleSaveLocked for clarity
- Document that scheduleSaveLocked must be called with lock held
- Add tests for new error tracking functionality
- Add Start/Stop lifecycle methods to AlertTriggeredAnalyzer
- Periodic cleanup of lastAnalyzed map every 30 minutes
- Prevents memory growth from stale cooldown entries
- Document that ai package feature constants are aliases of license constants
- Call Start() in StartPatrol and Stop() in StopPatrol
- Add tests for Start/Stop lifecycle
- Add missing KubernetesChecked field to persistence (data was being lost)
- Fix Duration field to properly convert between ms and nanoseconds
- Add automatic cleanup of stale stream subscribers (memory leak fix)
- Add error tracking for findings persistence with callback support
- Add GetPersistenceStatus() and SetOnSaveError() methods
- Add tests for new error tracking functionality
This update integrates learned baselines into the heuristic analysis to detect abnormal behavior and records significant events (migrations, restarts, spikes) for correlation analysis. Also fixed syntax errors in Ollama integration tests.
This update integrates learned baselines into the heuristic analysis to detect abnormal behavior and records significant events (migrations, restarts, spikes) for correlation analysis.
- Add persistent volume mounts for Go/npm caches (faster rebuilds)
- Add shell config with helpful aliases and custom prompt
- Add comprehensive devcontainer documentation
- Add pre-commit hooks for Go formatting and linting
- Use go-version-file in CI workflows instead of hardcoded versions
- Simplify docker compose commands with --wait flag
- Add gitignore entries for devcontainer auth files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The sensor proxy self-heal script runs every 5 minutes and calls migrate-to-file.
Previously it would print 'Migration complete' every time, even when already in
file mode with nothing to migrate.
Now migrateInlineToFile returns a boolean indicating if migration actually
occurred, and the CLI only prints the message when work was done.
Synology NAS creates multiple shared folders (e.g., /volume1/docker, /volume1/photos)
that are all mount points on the same underlying BTRFS volume. Each reported the same
16TB total, causing Pulse to show 64TB+ instead of 16TB.
The fix tracks device+total combinations and only counts each unique pair once.
When duplicates are found, the shallowest mountpoint (e.g., /volume1) is preferred.
Added a unit test to verify the deduplication works correctly.
Previously, toggling AI Commands in the Agents view would show a pending state
and wait for the agent to confirm the change (up to 2 minutes). If the agent
was slow to report or the WebSocket update was missed, the toggle would appear
stuck.
Now, UpdateHostAgentConfig also updates the Host model in state immediately,
providing instant UI feedback. The agent will still receive the config on its
next report, but users see the change right away.
Added SetHostCommandsEnabled function to models.State for this purpose.
Fixed an issue where all Docker containers were showing 'click to update'
even when they were up to date. The root cause was comparing the wrong
digest types:
- Previously: Compared ImageID (local config hash) vs registry manifest digest
- Now: Uses RepoDigests from image inspect, which is the actual manifest
digest that Docker received from the registry when pulling the image
For multi-arch images, the registry returns a manifest list digest, while
Docker stores the platform-specific image config digest locally. These
will never match, causing false positives for all multi-arch images.
Changes:
- Added ImageInspectWithRaw to dockerClient interface
- Added getImageRepoDigest method to extract RepoDigest from image
- Added matchesImageReference helper for Docker Hub naming conventions
- Added tests for matchesImageReference
Fixes#955
Fixed a critical reactivity bug in HostsOverview.tsx where the HostRow
component was destructuring props.host in the function body. In SolidJS,
this breaks reactivity because the destructured value is a static snapshot
captured at component creation time.
Changes:
- Removed 'const { host } = props' destructuring in HostRow
- Changed all 'host.' references to 'props.host.' to maintain reactivity
- Converted cpuPercent and diskStats to reactive getters (functions)
- Added documentation comment explaining why destructuring breaks reactivity
This fixes Issue #949 where CPU, memory, and disk values on the Hosts
page would stay stale until manual page refresh.
Related to #949
- Add critical release section at top of file
- Make VERSION file requirement impossible to miss
- Explain that version_guard job will fail if VERSION doesn't match
- Add Undismiss() method to FindingsStore to revert dismissed findings
- Include all dismissed findings in GetSuppressionRules() (not just suppressed)
- Add DismissedReason field to SuppressionRule struct
- Update DeleteSuppressionRule to handle dismissed (non-suppressed) findings
- Update frontend to show dismissal type badges (Suppressed/Expected/Noted)
- Change 'Delete' button to 'Reactivate' for dismissed findings
Related to #950
Content was being streamed twice:
1. During each iteration of the tool loop (intended for intermediate feedback)
2. Again after the loop ended with finalContent (redundant)
This caused duplicate responses when using Ollama and other providers.
BREAKING CHANGE: AI Patrol now uses EXACT alert thresholds by default
instead of warning 5-15% before the threshold.
Changes:
- Default behavior: Patrol warns at your configured threshold (e.g., 96% = warns at 96%)
- New setting: 'use_proactive_thresholds' enables the old early-warning behavior
- API: Added use_proactive_thresholds to GET/PUT /api/settings/ai
- Backend: Added SetProactiveMode/GetProactiveMode to PatrolService
- Backend: Added GetThresholds to PatrolService for UI display
- Tests: Updated and added tests for both exact and proactive modes
- Also fixed unused imports in dockeragent/agent.go
When proactive mode is disabled (default):
- Watch: threshold - 5% (slight buffer)
- Warning: exact threshold
When proactive mode is enabled:
- Watch: threshold - 15%
- Warning: threshold - 5%
Related to #951
- patrol.go: Auto-fix now requires both config flag AND ai_autofix license
- service.go: IsAutonomous() checks for ai_autofix license before enabling
- ai_handlers.go: API returns 402 if enabling auto-fix/autonomous without license
Users who accumulated AI patrol findings before the patrol-without-AI
bug was fixed (24c4bb0b) could not dismiss them because the dismiss and
resolve endpoints required a Pro license.
Changes:
- Remove Pro license requirement from /api/ai/patrol/dismiss endpoint
- Remove Pro license requirement from /api/ai/patrol/resolve endpoint
- Add ClearAll() method to FindingsStore for bulk clearing
- Add DELETE /api/ai/patrol/findings endpoint for clearing all findings
- Add "Clear All" button to AI Insights UI
Users can now dismiss or resolve any findings they can see, and admins
can clear all findings at once if needed.
Adds RequestTimeoutSeconds to AI config (default 300s / 5 min).
Users with low-power hardware running Ollama can increase this
value in Settings to prevent timeouts on slower inference.
Alert-triggered AI analysis was passing nil for lastBackup when analyzing
guests, causing 'Never backed up' findings even when backup data existed.
- Pass actual LastBackup timestamp from VM/Container state in analyzeGuestFromAlert
- Add regression test to verify backup data is correctly passed through
Fixes false positive 'Never backed up' alerts appearing when CPU/memory alerts fire.
Adds structured XML finding responses to the demo mock AI service.
This prevents the background patrol service from failing with 'Analysis failed'
when running in demo mode without a real LLM provider.
Updates the demo data generator to create patrol runs spaced 6 hours apart
over the last 3 days, rather than clustering them all in the last hour.
This provides a more authentic viewing experience on the demo dashboard.
Ensures the AI settings endpoint reports enabled=true and configured=true
when running in demo mode (PULSE_MOCK_MODE=true), even if no provider is
configured. This unlocks the frontend UI to allow interaction with the
mock AI assistant.
Allows the AI Assistant to provide realistic canned responses on the
live demo server without needing a real API key. Handled automatically
when PULSE_MOCK_MODE=true and no provider is configured.
- Add validateAIFindings() that cross-checks AI findings against actual metrics
- Filter out low-confidence findings (CPU <50%, memory <60%, disk <70%)
- Always allow critical findings, backup issues, and reliability findings through
- Update AI system prompt with stricter thresholds and explicit noise examples
- Add 'before creating a finding' checklist for AI (the 3am test)
- Update AI.md docs with clear value proposition and expectations
- Add comprehensive tests for the validation layer
This ensures paying users get immediate value without noise.
- Add IsMockResource() helper to detect mock data resources by naming patterns
- Filter out heuristic findings from mock resources when PULSE_MOCK_MODE is off
- Mock findings still appear when demo mode is enabled
- Ensures users don't see mock data findings in production
The AI demo/mock findings were using a non-existent MOCK_ENABLED
env var. Changed to PULSE_MOCK_MODE which is the actual env var
used by the mock data system.
- Add AnalyzeKubernetes to PatrolConfig and enable by default
- Add analyzeKubernetesCluster() for K8s heuristic analysis
- Detect offline clusters, nodes not ready, CrashLoopBackOff pods
- Detect failed/pending pods and unavailable deployments
- Add K8s clusters to LLM context in buildInfrastructureSummary()
- Add kubernetes_cluster as valid resource type in AI prompt
- Enhance analyzeDockerHost() for Docker/Podman
- Podman-aware messaging based on runtime
- Add unhealthy container detection (health checks)
- Add exited container with error detection
- Add high CPU detection (>90%)
- Add stale host detection (10+ minutes)
- Tiered severity for restarts (>10 = critical)
- Add 20 new tests for K8s and Docker analysis
When MOCK_ENABLED=true, Pulse now injects realistic AI patrol
findings to showcase the AI features without requiring actual
LLM API calls. This enables the demo instance to demonstrate:
- Critical/warning/info findings with realistic content
- Patrol run history
- Actionable recommendations
Also includes refinements to dismissal logic from earlier work:
- Only 'not_an_issue' creates permanent suppression
- 'expected_behavior' and 'will_fix_later' just acknowledge
1. Fixed TestNewConfigPersistenceFailsWhenEncryptedDataPresentWithoutKey
- Test was picking up real encryption key from /etc/pulse during migration
- Now temporarily moves system key during test for proper isolation
- Uses t.Cleanup to ensure key is restored even on failure
2. Cleaned up console.log statements in production code
- Dashboard.tsx: replaced console.log with logger.debug for metadata events
- CompleteStep.tsx: removed verbose agent detection debug logs
These changes reduce log noise in production while maintaining debug
capability in development mode.
Addresses #866 - agents were logging 'WebSocket connection failed' warnings
even during normal reconnection scenarios (server restart, network blip, etc).
Changes:
- Normal close errors (1000, 1001, connection reset) now log at Debug level
- Only log Warning after 3+ consecutive failures
- Changed 'Connecting to Pulse' from Info to Debug to reduce noise
- Successful connections still log at Info level
The WebSocket is only used for AI command execution, not metrics, so
transient disconnections don't affect monitoring functionality.
User feedback fields (DismissedReason, UserNote, TimesRaised, Suppressed, Source)
were not being saved to disk, causing 'expected behavior' dismissals to be lost
after Pulse restarted.
- Add missing fields to AIFindingRecord in persistence.go
- Update FindingsPersistenceAdapter to save/load these fields
- Add comprehensive tests for dismissal persistence round-trip
Fixes issue where Frigate storage warning kept reappearing despite being
marked as expected behavior.
Since watch/info findings are filtered from the UI and never shown
to users, don't include them in the patrol run status summary.
This makes the summary consistent with what users actually see.
The LLM was confusing VMIDs because they weren't included in the
context. Now the formatted context shows:
### Container: ollama (VMID 200) on minipc
This prevents the AI from referencing the wrong VMID when generating
findings and recommendations.
When the service restarts, it now checks if a patrol ran within the
last hour. If so, it skips the initial patrol to avoid wasting API
tokens during development/maintenance when the service is restarted
frequently.
The scheduled patrol runs (every 6 hours) are not affected.
100 samples was causing 326k+ input tokens which is expensive.
24 samples (hourly resolution) still provides good pattern visibility
while significantly reducing token cost.
Estimated reduction: ~75% fewer metric tokens.
When AI patrol fails due to API issues like insufficient balance, invalid
API key, or rate limiting, we now create a finding that appears in the
AI Insights tab. This makes the issue visible to users rather than hidden
in logs.
The finding includes:
- Clear description of the issue (e.g., 'Insufficient API credits')
- Recommendation for how to fix it
- Evidence showing the actual error message
When a patrol run encounters errors (e.g., LLM call failed), don't
display 'All healthy' in the summary as that's misleading - the
analysis didn't complete properly.
Now shows 'Analysis incomplete (N errors)' instead, which correctly
explains why the status badge shows red/error.
Modern LLMs have 100k+ token contexts. 100 samples over 24h gives
~15 minute resolution while adding minimal token overhead.
This lets the LLM see fine-grained patterns, short spikes, and
accurately distinguish anomalies from normal behavior.
The in-memory MetricsHistory only retains 24 hours of data, not 7 days.
Changed computeGuestMetricSamples to use trendWindow24h instead of
trendWindow7d, and reduced sample count from 24 to 12 points.
This ensures the LLM actually receives metric samples in the context,
which wasn't happening before because the 7-day query returned empty data.