Skip to content

Commit 2e34949

Browse files
committed
Keep latest-session timestamps increasing under tight loops
The next repo-local sweep target was ROADMAP ultraworkers#73: repeated backlog sweeps exposed that session writes could share the same wall-clock millisecond, which made semantic recency fragile and forced the resume-latest regression to sleep between saves. The fix makes session timestamps monotonic within the process and removes the timing hack from the test so latest-session selection stays stable under tight loops. Constraint: Preserve the existing session file format while changing only the timestamp source semantics Rejected: Keep the sleep-based test workaround | hides the real ordering hazard instead of fixing timestamp generation Confidence: high Scope-risk: narrow Reversibility: clean Directive: Any future session-recency logic must keep `current_time_millis`, ordering tests, and latest-session expectations aligned Tested: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace; architect review APPROVE Not-tested: Cross-process monotonicity when multiple binaries write sessions concurrently
1 parent 8f53524 commit 2e34949

3 files changed

Lines changed: 33 additions & 7 deletions

File tree

ROADMAP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,3 +517,4 @@ Model name prefix now wins unconditionally over env-var presence. Regression tes
517517
71. **Wrong-task prompt receipt is not detected before execution****done (verified 2026-04-12):** worker boot prompt dispatch now accepts an optional structured `task_receipt` (`repo`, `task_kind`, `source_surface`, `expected_artifacts`, `objective_preview`) and treats mismatched visible prompt context as a `WrongTask` prompt-delivery failure before execution continues. The prompt-delivery payload now records `observed_prompt_preview` plus the expected receipt, and regression coverage locks both the existing shell/wrong-target paths and the new KakaoTalk-style wrong-task mismatch case. **Original filing below.**
518518

519519
72. **`latest` managed-session selection depends on filesystem mtime before semantic session recency****done (verified 2026-04-12):** managed-session summaries now carry `updated_at_ms`, `SessionStore::list_sessions()` sorts by semantic recency before filesystem mtime, and regression coverage locks the case where `latest` must prefer the newer session payload even when file mtimes point the other way. The CLI session-summary wrapper now stays in sync with the runtime field so `latest` resolution uses the same ordering signal everywhere. **Original filing below.**
520+
73. **Session timestamps are not monotonic enough for latest-session ordering under tight loops****done (verified 2026-04-12):** runtime session timestamps now use a process-local monotonic millisecond source, so back-to-back saves still produce increasing `updated_at_ms` even when the wall clock does not advance. The temporary sleep hack was removed from the resume-latest regression, and fresh workspace verification stayed green with the semantic-recency ordering path from #72. **Original filing below.**

rust/crates/runtime/src/session.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const SESSION_VERSION: u32 = 1;
1313
const ROTATE_AFTER_BYTES: u64 = 256 * 1024;
1414
const MAX_ROTATED_FILES: usize = 3;
1515
static SESSION_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
16+
static LAST_TIMESTAMP_MS: AtomicU64 = AtomicU64::new(0);
1617

1718
/// Speaker role associated with a persisted conversation message.
1819
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -1030,10 +1031,27 @@ fn normalize_optional_string(value: Option<String>) -> Option<String> {
10301031
}
10311032

10321033
fn current_time_millis() -> u64 {
1033-
SystemTime::now()
1034+
let wall_clock = SystemTime::now()
10341035
.duration_since(UNIX_EPOCH)
10351036
.map(|duration| u64::try_from(duration.as_millis()).unwrap_or(u64::MAX))
1036-
.unwrap_or_default()
1037+
.unwrap_or_default();
1038+
1039+
let mut candidate = wall_clock;
1040+
loop {
1041+
let previous = LAST_TIMESTAMP_MS.load(Ordering::Relaxed);
1042+
if candidate <= previous {
1043+
candidate = previous.saturating_add(1);
1044+
}
1045+
match LAST_TIMESTAMP_MS.compare_exchange(
1046+
previous,
1047+
candidate,
1048+
Ordering::SeqCst,
1049+
Ordering::SeqCst,
1050+
) {
1051+
Ok(_) => return candidate,
1052+
Err(actual) => candidate = actual.saturating_add(1),
1053+
}
1054+
}
10371055
}
10381056

10391057
fn generate_session_id() -> String {
@@ -1125,15 +1143,25 @@ fn cleanup_rotated_logs(path: &Path) -> Result<(), SessionError> {
11251143
#[cfg(test)]
11261144
mod tests {
11271145
use super::{
1128-
cleanup_rotated_logs, rotate_session_file_if_needed, ContentBlock, ConversationMessage,
1129-
MessageRole, Session, SessionFork,
1146+
cleanup_rotated_logs, current_time_millis, rotate_session_file_if_needed, ContentBlock,
1147+
ConversationMessage, MessageRole, Session, SessionFork,
11301148
};
11311149
use crate::json::JsonValue;
11321150
use crate::usage::TokenUsage;
11331151
use std::fs;
11341152
use std::path::{Path, PathBuf};
11351153
use std::time::{SystemTime, UNIX_EPOCH};
11361154

1155+
#[test]
1156+
fn session_timestamps_are_monotonic_under_tight_loops() {
1157+
let first = current_time_millis();
1158+
let second = current_time_millis();
1159+
let third = current_time_millis();
1160+
1161+
assert!(first < second);
1162+
assert!(second < third);
1163+
}
1164+
11371165
#[test]
11381166
fn persists_and_restores_session_jsonl() {
11391167
let mut session = Session::new();

rust/crates/rusty-claude-cli/tests/resume_slash_commands.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ use std::path::Path;
33
use std::path::PathBuf;
44
use std::process::{Command, Output};
55
use std::sync::atomic::{AtomicU64, Ordering};
6-
use std::thread;
7-
use std::time::Duration;
86
use std::time::{SystemTime, UNIX_EPOCH};
97

108
use runtime::ContentBlock;
@@ -193,7 +191,6 @@ fn resume_latest_restores_the_most_recent_managed_session() {
193191
older
194192
.save_to_path(&older_path)
195193
.expect("older session should persist");
196-
thread::sleep(Duration::from_millis(2));
197194

198195
let mut newer = workspace_session(&project_dir).with_persistence_path(&newer_path);
199196
newer

0 commit comments

Comments
 (0)