mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-13 03:24:49 +08:00
Keep latest-session timestamps increasing under tight loops
The next repo-local sweep target was ROADMAP #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
This commit is contained in:
@@ -517,3 +517,4 @@ Model name prefix now wins unconditionally over env-var presence. Regression tes
|
|||||||
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.**
|
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.**
|
||||||
|
|
||||||
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.**
|
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.**
|
||||||
|
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.**
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const SESSION_VERSION: u32 = 1;
|
|||||||
const ROTATE_AFTER_BYTES: u64 = 256 * 1024;
|
const ROTATE_AFTER_BYTES: u64 = 256 * 1024;
|
||||||
const MAX_ROTATED_FILES: usize = 3;
|
const MAX_ROTATED_FILES: usize = 3;
|
||||||
static SESSION_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
|
static SESSION_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||||
|
static LAST_TIMESTAMP_MS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
/// Speaker role associated with a persisted conversation message.
|
/// Speaker role associated with a persisted conversation message.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -1030,10 +1031,27 @@ fn normalize_optional_string(value: Option<String>) -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn current_time_millis() -> u64 {
|
fn current_time_millis() -> u64 {
|
||||||
SystemTime::now()
|
let wall_clock = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.map(|duration| u64::try_from(duration.as_millis()).unwrap_or(u64::MAX))
|
.map(|duration| u64::try_from(duration.as_millis()).unwrap_or(u64::MAX))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut candidate = wall_clock;
|
||||||
|
loop {
|
||||||
|
let previous = LAST_TIMESTAMP_MS.load(Ordering::Relaxed);
|
||||||
|
if candidate <= previous {
|
||||||
|
candidate = previous.saturating_add(1);
|
||||||
|
}
|
||||||
|
match LAST_TIMESTAMP_MS.compare_exchange(
|
||||||
|
previous,
|
||||||
|
candidate,
|
||||||
|
Ordering::SeqCst,
|
||||||
|
Ordering::SeqCst,
|
||||||
|
) {
|
||||||
|
Ok(_) => return candidate,
|
||||||
|
Err(actual) => candidate = actual.saturating_add(1),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_session_id() -> String {
|
fn generate_session_id() -> String {
|
||||||
@@ -1125,8 +1143,8 @@ fn cleanup_rotated_logs(path: &Path) -> Result<(), SessionError> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
cleanup_rotated_logs, rotate_session_file_if_needed, ContentBlock, ConversationMessage,
|
cleanup_rotated_logs, current_time_millis, rotate_session_file_if_needed, ContentBlock,
|
||||||
MessageRole, Session, SessionFork,
|
ConversationMessage, MessageRole, Session, SessionFork,
|
||||||
};
|
};
|
||||||
use crate::json::JsonValue;
|
use crate::json::JsonValue;
|
||||||
use crate::usage::TokenUsage;
|
use crate::usage::TokenUsage;
|
||||||
@@ -1134,6 +1152,16 @@ mod tests {
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn session_timestamps_are_monotonic_under_tight_loops() {
|
||||||
|
let first = current_time_millis();
|
||||||
|
let second = current_time_millis();
|
||||||
|
let third = current_time_millis();
|
||||||
|
|
||||||
|
assert!(first < second);
|
||||||
|
assert!(second < third);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn persists_and_restores_session_jsonl() {
|
fn persists_and_restores_session_jsonl() {
|
||||||
let mut session = Session::new();
|
let mut session = Session::new();
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ use std::path::Path;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{Command, Output};
|
use std::process::{Command, Output};
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use runtime::ContentBlock;
|
use runtime::ContentBlock;
|
||||||
@@ -193,7 +191,6 @@ fn resume_latest_restores_the_most_recent_managed_session() {
|
|||||||
older
|
older
|
||||||
.save_to_path(&older_path)
|
.save_to_path(&older_path)
|
||||||
.expect("older session should persist");
|
.expect("older session should persist");
|
||||||
thread::sleep(Duration::from_millis(2));
|
|
||||||
|
|
||||||
let mut newer = workspace_session(&project_dir).with_persistence_path(&newer_path);
|
let mut newer = workspace_session(&project_dir).with_persistence_path(&newer_path);
|
||||||
newer
|
newer
|
||||||
|
|||||||
Reference in New Issue
Block a user