From 35f14c7e02e671fce8530e0682a7669b6eeaa83a Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Wed, 15 Jul 2026 14:58:39 +0530 Subject: [PATCH] fix(automations): use microsecond-integer stamp for status episode keys too Same float64 imprecision as the awaiting-agent key: a status episode armed from an in-memory status_changed_at (Time.current, nanosecond) could recompute to a slightly different float once the worker reloads the DB-rounded value, skipping the row as episode_moved. Use the shared microsecond_stamp helper so both sides agree. --- app/models/automation_rule_pending_execution.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/automation_rule_pending_execution.rb b/app/models/automation_rule_pending_execution.rb index aaf9f29ee..372cef7f6 100644 --- a/app/models/automation_rule_pending_execution.rb +++ b/app/models/automation_rule_pending_execution.rb @@ -70,8 +70,8 @@ class AutomationRulePendingExecution < ApplicationRecord end # Microsecond integer, not a float: epoch seconds carry ~16 significant digits, past float64's - # precision, and this key compares an in-memory created_at against the DB-stored waiting_since, - # so a float would round differently on either side. Sub-second distinguishes rapid re-waits. + # precision, so an in-memory timestamp (arm time) and its DB-reloaded value (fire time) would + # round to different floats. strftime is exact on both. Sub-second distinguishes rapid episodes. def self.microsecond_stamp(time) time&.strftime('%s%6N') || '0' end @@ -81,7 +81,8 @@ class AutomationRulePendingExecution < ApplicationRecord def self.episode_key_for(conversation, message) if message.nil? # Sub-second precision so a resolve→reopen inside one second still ends the episode. - "status:#{(conversation.status_changed_at.presence || conversation.created_at).to_f}" + # Integer microseconds (not a float) so an in-memory arm and a DB-reloaded fire agree. + "status:#{microsecond_stamp(conversation.status_changed_at.presence || conversation.created_at)}" elsif message.incoming? # waiting_since is cleared on agent/bot reply, so a reply invalidates this episode. Strict # here: at fire time a nil waiting_since means the agent replied (episode ended).