From 53b84826c6a4755c7e2eae77cdd81ce8b679efa8 Mon Sep 17 00:00:00 2001 From: tmoonlight Date: Sat, 10 Oct 2026 01:48:51 +0800 Subject: [PATCH 1/2] Keep checked transcript appends within session file-size limits --- .../changes/persist-session-error-notices/design.md | 1 + .../changes/persist-session-error-notices/tasks.md | 1 + src/domain/session/session_manager.cpp | 11 +---------- src/domain/session/session_manager.hpp | 1 + src/domain/session/session_manager_history.cpp | 13 +++++++++++++ 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/openspec/changes/persist-session-error-notices/design.md b/openspec/changes/persist-session-error-notices/design.md index 96e4fb13..00bc4a8f 100644 --- a/openspec/changes/persist-session-error-notices/design.md +++ b/openspec/changes/persist-session-error-notices/design.md @@ -15,6 +15,7 @@ See proposal.md for the incident. Agent error messages use `TranscriptWriter::di 3. Distinguish durable errors by `role=error`, `metadata.transcript_only=true`, and a nonempty ID. Reconcile these by ID in both replay paths; bypass content-only deduplication for different error IDs. Unmarked legacy errors retain occurrence/sequence semantics. 4. Reuse the existing provider-history filter, and test both the actual next provider request and resumed history. No UI style changes or additional context protocol is required. 5. When a storage failure causes an error notice, retain that original storage diagnostic while attempting to save the notice. Failed compaction preserves the previous history prefix and context window; its new transcript-only error is visible without pretending the failed checkpoint was stored. +6. Keep the public checked-append lock and diagnostic guard in the existing session history implementation. Delegate the actual append to a private helper requiring that lock, keeping the main session manager within its established file-size limit without changing append behavior. ## Risks / Trade-offs diff --git a/openspec/changes/persist-session-error-notices/tasks.md b/openspec/changes/persist-session-error-notices/tasks.md index c2a79fc9..336c56e2 100644 --- a/openspec/changes/persist-session-error-notices/tasks.md +++ b/openspec/changes/persist-session-error-notices/tasks.md @@ -8,3 +8,4 @@ - [x] 2.1 Document the additive daemon message contract and verify strict OpenSpec validation plus diff checks. - [x] 2.2 Run relevant Windows native regression tests, full Web tests, Web build, and a browser check of restored red error cards; record results and runtime limitations. - [x] 2.3 Preserve the original storage failure when persisting its error notice also fails, update blocked-input/failed-compaction expectations for transcript-only errors, and pass focused plus full Windows release regressions. +- [ ] 2.4 Keep the checked-append facade in the existing history implementation to satisfy the file-size limit; verify all strict gates individually and rerun native session regressions after the move. diff --git a/src/domain/session/session_manager.cpp b/src/domain/session/session_manager.cpp index 68f9f8a8..012b6ba3 100644 --- a/src/domain/session/session_manager.cpp +++ b/src/domain/session/session_manager.cpp @@ -16,7 +16,6 @@ #include "session_usage_ledger.hpp" #include "utils/atomic_file.hpp" #include "utils/logger.hpp" -#include "utils/scope_exit.hpp" #include "utils/uuid.hpp" #include "utils/utf8_path.hpp" @@ -340,15 +339,7 @@ bool SessionManager::ensure_created() { return true; } -bool SessionManager::try_on_message(const ChatMessage& msg) { - std::lock_guard lk(mu_); - // Recording an error notice must not replace the storage failure that - // caused it, even when that notice also cannot be written to disk. - std::string original_error = msg.role == "error" && msg.metadata.is_object() && - msg.metadata.value("transcript_only", false) ? last_error_ : std::string{}; - ScopeExit preserve_error([this, original_error = std::move(original_error)]() mutable { - if (!original_error.empty()) last_error_.swap(original_error); - }); +bool SessionManager::try_on_message_locked(const ChatMessage& msg) { last_error_.clear(); if (!started_ || !ensure_created()) return false; diff --git a/src/domain/session/session_manager.hpp b/src/domain/session/session_manager.hpp index 58d4b6cd..e489dc78 100644 --- a/src/domain/session/session_manager.hpp +++ b/src/domain/session/session_manager.hpp @@ -296,6 +296,7 @@ class SessionManager { std::string current_trajectory_path() const; private: + bool try_on_message_locked(const ChatMessage& msg); // Requires mu_. void record_recent_activity_locked(const ChatMessage& msg); void restore_recent_activity_locked(const SessionMeta& persisted_meta); void publish_display_snapshot_locked(); // mu_ held; display_mu_ is a leaf lock. diff --git a/src/domain/session/session_manager_history.cpp b/src/domain/session/session_manager_history.cpp index ffb08d61..76491759 100644 --- a/src/domain/session/session_manager_history.cpp +++ b/src/domain/session/session_manager_history.cpp @@ -3,6 +3,7 @@ #include "turn_timing.hpp" #include "session_history_page.hpp" #include "utils/logger.hpp" +#include "utils/scope_exit.hpp" #include namespace acecode { @@ -12,6 +13,18 @@ void SessionManager::on_message(const ChatMessage& msg) { (void)try_on_message(msg); } +bool SessionManager::try_on_message(const ChatMessage& msg) { + std::lock_guard lk(mu_); + // Recording an error notice must not replace the storage failure that + // caused it, even when that notice also cannot be written to disk. + std::string original_error = msg.role == "error" && msg.metadata.is_object() && + msg.metadata.value("transcript_only", false) ? last_error_ : std::string{}; + ScopeExit preserve_error([this, original_error = std::move(original_error)]() mutable { + if (!original_error.empty()) last_error_.swap(original_error); + }); + return try_on_message_locked(msg); +} + void SessionManager::record_turn_outcome(const std::string& outcome) { if (!is_valid_turn_timing_status(outcome)) return; std::lock_guard lock(mu_); From dbc222212eec9d5c19c4fb8acbe7d8e8935ee8e5 Mon Sep 17 00:00:00 2001 From: tmoonlight Date: Sat, 10 Oct 2026 01:52:09 +0800 Subject: [PATCH 2/2] Record final transcript persistence regression verification --- openspec/changes/persist-session-error-notices/tasks.md | 2 +- openspec/changes/persist-session-error-notices/verification.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openspec/changes/persist-session-error-notices/tasks.md b/openspec/changes/persist-session-error-notices/tasks.md index 336c56e2..a869aa72 100644 --- a/openspec/changes/persist-session-error-notices/tasks.md +++ b/openspec/changes/persist-session-error-notices/tasks.md @@ -8,4 +8,4 @@ - [x] 2.1 Document the additive daemon message contract and verify strict OpenSpec validation plus diff checks. - [x] 2.2 Run relevant Windows native regression tests, full Web tests, Web build, and a browser check of restored red error cards; record results and runtime limitations. - [x] 2.3 Preserve the original storage failure when persisting its error notice also fails, update blocked-input/failed-compaction expectations for transcript-only errors, and pass focused plus full Windows release regressions. -- [ ] 2.4 Keep the checked-append facade in the existing history implementation to satisfy the file-size limit; verify all strict gates individually and rerun native session regressions after the move. +- [x] 2.4 Keep the checked-append facade in the existing history implementation to satisfy the file-size limit; verify all strict gates individually and rerun native session regressions after the move. diff --git a/openspec/changes/persist-session-error-notices/verification.md b/openspec/changes/persist-session-error-notices/verification.md index 5a0ddd3d..ca377256 100644 --- a/openspec/changes/persist-session-error-notices/verification.md +++ b/openspec/changes/persist-session-error-notices/verification.md @@ -15,3 +15,4 @@ The browser check uses isolated simulated responses, not the affected remote ser - Full Windows regression exposed two old assumptions that failures leave no visible error record, plus a diagnostic regression when saving the notice also failed. Retained the original storage error under the session lock, and updated blocked-input and failed-compaction assertions to require transcript-only errors while preserving the original history prefix and context window. - Focused release regressions: 41 passed, zero failures. - Final full Windows regression: 6239 inventory entries, 6238 executed, 6229 passed, 9 skipped, zero failures; one disabled benchmark. Ran all suites with `run_fast_tests.py --profile full --shards 6` and isolated profiles. Report: `C:/Users/shao/AppData/Local/Temp/acecode-release-1.0.3/windows-full-final.json`. +- The CI file-size gate then rejected eight added lines in the main session manager. Moved the public checked-append lock/diagnostic facade into the existing history implementation and retained a private locked append helper. All seven strict gates passed individually with fail-fast handling, including the cloud layer gate. Rebuilt Windows tests and passed 91 session, storage, history, hook, reasoning, and payload regressions after this structural move (`final-layout-tests.json`).