fix(desktop): debounce staged task promotion#7592
Conversation
Greptile SummaryThis PR adds a 30-second debounce to
Confidence Score: 4/5Safe to merge; the debounce logic is correct and the actor serialization is sound. The safety-net timer can silently skip one cycle after an event-driven promotion, but this only affects recovery latency for missed promotions, not correctness. The core change is small and well-reasoned. The two concerns are the shared debounce gate between event-driven calls and the safety-net timer (which can push safety-net latency from 60 s to 120 s in edge cases) and the late stamping of lastPromotedAt after async side-effects. Neither causes data loss or incorrect promotions; they only affect timing guarantees of the background safety net. desktop/Desktop/Sources/ProactiveAssistants/Assistants/TaskExtraction/TaskPromotionService.swift — specifically the interaction between the debounce guard and the safety-net timer call site in startSafetyTimer(). Important Files Changed
Sequence DiagramsequenceDiagram
participant E as Event (complete/delete)
participant TPS as TaskPromotionService
participant API as APIClient
participant ST as SafetyTimer (60s)
Note over TPS: lastPromotedAt = distantPast
TPS->>TPS: start() calls promoteIfNeeded()
TPS->>TPS: 30s check passes (distantPast)
TPS->>API: promoteTopStagedTask()
API-->>TPS: "promoted=true, task A"
TPS->>TPS: "lastPromotedAt = t5"
E->>TPS: promoteIfNeeded() at t20
TPS->>TPS: "15s < 30s, DEBOUNCED, return []"
E->>TPS: promoteIfNeeded() at t36
TPS->>TPS: "31s >= 30s, passes"
TPS->>API: promoteTopStagedTask()
API-->>TPS: "promoted=true, task B"
TPS->>TPS: "lastPromotedAt = t36"
ST->>TPS: timer tick at t65
TPS->>TPS: "29s < 30s, DEBOUNCED, return []"
Note over ST: next tick at t125, not t65
Reviews (1): Last reviewed commit: "fix(desktop): debounce staged task promo..." | Re-trigger Greptile |
|
Addressed Greptile's two debounce comments in follow-up commit |
39389e3 to
52c3cc7
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Summary
TaskPromotionService.promoteIfNeeded()Closes #7439
Closes #7441
Why
The desktop app currently calls
promoteIfNeeded()from five places:The actor already prevents overlapping calls with
isPromoting, but it does not debounce sequential calls. If several triggers fire a few seconds apart, each one still reachesPOST /v1/staged-tasks/promote, which contributes to the Firestore write spike described in #7439/#7441.Implementation details
promotionDebounceInterval = 30secondslastPromotedAtinside the actor, so no extra locking is neededThat last point matters: if there is no staged task at startup, the first later real staged task promotion is not blocked by a stale no-op cooldown.
Relation to existing work
This is complementary to backend dedup/idempotency work like #7092/#7093. Those PRs reduce duplicate task creation. This PR reduces the desktop call frequency that was causing promote endpoint write amplification.
Verification
xcrun swift build -c debug --package-path Desktop