From b95c594be8c0bcd3461cd00f4889567d15708a66 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 1 Nov 2026 03:06:38 +0100 Subject: [PATCH] fix(session): log --last 0 means no backlog debug.log handed `last` to the timeline query, where 0 means every entry, so `log --follow --last 0` streamed the whole ring of up to 8192 entries before the new ones, where an agent reads it as tail -n 0 -f: nothing old, only what comes next. 0 now means no backlog, followed or not, and the guide says that --follow starts with the last 50. Co-Authored-By: Claude Opus 5.5 --- implementations/Crafter.Debug-Session.cpp | 7 +++++-- implementations/Crafter.Debug.Cli-Client.cpp | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/implementations/Crafter.Debug-Session.cpp b/implementations/Crafter.Debug-Session.cpp index 0176f47..14d206d 100644 --- a/implementations/Crafter.Debug-Session.cpp +++ b/implementations/Crafter.Debug-Session.cpp @@ -462,11 +462,14 @@ namespace Crafter::Debug { } if (!known) return exchange->Reply(MakeErrorReply(Codes::invalidParams, std::format("kinds: '{}' is not one of log, warn, error, check, event, agent, pause, stdio", kind))); } + // `last` 0 means none, as with tail -n 0: a follower that only + // wants what comes next. (A TimelineQuery's 0 means all.) Detail::TimelineQuery backlog = query; backlog.last = decoded->last; + const auto recent = [&] { return decoded->last == 0 ? std::vector() : Detail::Timeline::Instance().Query(backlog); }; if (!decoded->follow) { Value entries = Value::MakeArray(); - for (const TimelineEntry& entry : Detail::Timeline::Instance().Query(backlog)) entries.Push(EntryValue(entry)); + for (const TimelineEntry& entry : recent()) entries.Push(EntryValue(entry)); return exchange->Reply(MakeReply(Object({ { "entries", std::move(entries) }, { "lastSequence", Value(Detail::Timeline::Instance().LastSequence()) } }))); } // The backlog and the registration happen under one lock, which @@ -474,7 +477,7 @@ namespace Crafter::Debug { // every entry once and in order. Stream never blocks. std::lock_guard lock(mutex); const std::uint64_t upTo = Detail::Timeline::Instance().LastSequence(); - for (const TimelineEntry& entry : Detail::Timeline::Instance().Query(backlog)) { + for (const TimelineEntry& entry : recent()) { if (entry.sequence > upTo) continue; if (!exchange->Stream(Object({ { "entry", EntryValue(entry) } }))) return; } diff --git a/implementations/Crafter.Debug.Cli-Client.cpp b/implementations/Crafter.Debug.Cli-Client.cpp index af2100f..6e03563 100644 --- a/implementations/Crafter.Debug.Cli-Client.cpp +++ b/implementations/Crafter.Debug.Cli-Client.cpp @@ -984,7 +984,8 @@ memory are a debugger's job; `crafter-debug debugger` prints how to attach one. trace --history a Debug::Sample's recent history log [--last 50] [--level info|warn|error] [--cat ] [--grep ] [--since-frame n] [--kinds log,warn,error,check,event,agent,pause,stdio] - log --follow [--for 30m] streams new entries; run it under Monitor + log --follow [--for 30m] the last 50 (--last; 0 for none), then new + entries as they come; run it under Monitor screenshot [-o shot.png] default ./build/crafter-debug-shots/ quit [--force] --force exits even if the loop is stuck debugger how to attach lldb or gdb