fix(automation): timed events firing across all sources#3479
Conversation
…ss-source firing)
Timer Triggers ("Timed Events") configured for one source were firing on all
sources. Root cause: updateTimerTriggerResult READ the per-source settings key
(getSettingForSource(this.sourceId, 'timerTriggers')) but WROTE the
un-namespaced GLOBAL key (setSetting('timerTriggers', ...)). On every fire it
copied that source's trigger list into the global key; the settings GET-merge
then surfaced that global value to other sources lacking a per-source override,
so the trigger got scheduled and fired everywhere.
The per-source helpers deliberately do not fall back to global (see #2839), so
read and write must both be source-scoped. Switch the write to
setSourceSetting(this.sourceId, 'timerTriggers', ...).
Adds a perSource regression test asserting the result write targets the
source-scoped key and never the global key.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job Code Review Complete ✅Review Checklist:
SummaryThis is an excellent fix for a critical multi-source bug. The implementation is correct, well-documented, and thoroughly tested. Code Quality & Best Practices ✅Strengths:
Code Quality:
Test Coverage ✅The new test file
Lines 73-77 and 95-98 are particularly good - they assert the global key is NOT written to. Security Considerations ✅
Performance Considerations ✅
Potential Issues & Edge CasesMinor Considerations (not blockers):
Verification of Fix Scope:I checked for other similar patterns in the codebase:
Recommendation: APPROVE ✅This fix:
The implementation demonstrates deep understanding of MeshMonitor's multi-source architecture and the subtle but critical difference between |
Bump version to 4.10.4 across package.json, package-lock.json, helm/meshmonitor/Chart.yaml, desktop/package.json, and desktop/src-tauri/tauri.conf.json. Promote CHANGELOG [Unreleased] to [4.10.4] and add the user-facing fixes merged since 4.10.3: timed-events cross-source firing (#3479), module config save for Traffic Management/Status Message (#3464), Auto Favorites firmware-support cache (#3482), MeshCore Share Contact error surfacing (#3481), and the Firefox-Android delivered-icon CSS fix (#3477). The air-quality particle-count fix (#3483) was already in [Unreleased]. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem
Users reported that Timed Events (Timer Triggers) configured for one source fire on all sources.
Root cause
updateTimerTriggerResult(src/server/meshtasticManager.ts) had an asymmetric read/write:Every time a timer fired, it copied that source's full trigger list into the global
timerTriggerssetting. The settings GET-merge (settingsRoutes.ts{ ...global, ...sourceSettings }) then surfaces that global value to any source that has no per-source override — so the trigger gets read, scheduled, and fired on those sources too. The scheduler and the fire action themselves are correctly per-source; the leak was purely this persistence write.This is the same class of bug as #2839 (multi-source automation spam), which is exactly why
getSettingForSource/setSourceSettingdeliberately do not fall back to global.Fix
Make the write source-scoped and symmetric with the read:
Adds
meshtasticManager.timerTriggerScope.perSource.test.tsasserting the result write targetssource:<id>:timerTriggersand never the global key (success and error paths).Scope notes
timerTriggersvalue, and anull-sourceId save fansrestartTimerSchedulerout to every manager. Those are pre-existing behaviors that are only dangerous because this write poisoned the global key — with this fix the global key is no longer written by firing. Happy to follow up on excluding automation keys from the global merge if you want belt-and-suspenders.timerTriggersand/or triggers copied into a source's namespace. This fix stops further contamination but does not retroactively clean those rows — let me know if you want a cleanup migration.Validation
tsc --noEmit: clean🤖 Generated with Claude Code