fix(resume): mount saved history under the shared workspace coordination lock

Since 23e9119caf (after v0.10.1) a resume or fork mount built the
exclusive Runtime worker authority, which takes the workspace coordination
flock exclusively and fails when any other Codewhale session in the same
folder holds it shared, so a second resumed session per workspace was
impossible ("another Codewhale process owns delegated coordination").
v0.10.1 only ever took the lock shared. The mount now builds the same
shared, warn-on-failure manager a new interactive session uses, after the
per-session store owner lock is held and on the owner thread, and attaches
it as the runtime's worker authority before any lookup, recovery or
mutation. The mount only reads the ledger; per-session serialization (the
live document lease, the store owner lock, the import guard and the
reservation) is unchanged, and `codewhale serve`, ACP and the in-process
task runtime keep the exclusive authority.

Verification: checked once on the final batch tree (parent db89057d84):
cargo fmt --check clean; tui lib nextest across route_budget,
provider_catalog_live, provider_lake, mounted_history_tests,
session_manager, client, core::engine, compaction, tools::subagent,
runtime_api, purge, commands::contract, tui::model_picker and whats_new
2757 passed, 0 failed; tui integration exec_turn_usage 11 passed, 0
failed; feature_registry 2 passed, 0 failed; clippy -p codewhale-tui
--lib --tests -D warnings with the CI allow-list clean; check-blocking-
calls-budget 540 sites, within budget. Not run: hosted CI and the
loopback product captures (those follow on the release build).

Refs #7042

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Opus 5.5 committed 2026-10-21 17:07:05 -07:00
1 parent fa7140c5d4
commit 4c8e2c297f
1 file changed
+21 -2
+21 -2
View File
@@ -10055,9 +10055,10 @@ async fn prepare_mounted_session(
let manager_config = RuntimeThreadManagerConfig::for_session(task_config.data_dir, &source_id);
let owner_config = prepared.config.clone();
let owner_workspace = saved.metadata.workspace.clone();
let worker_workspace = owner_workspace.clone();
let prior_binding = saved.metadata.runtime_store.clone();
let recovery = cli.operation_key.is_some();
let runtime = history_owner_work(move || {
let (runtime, workers) = history_owner_work(move || {
// Recheck the captured binding immediately before the actual factory.
if let Some(binding) = prior_binding.as_ref() {
binding.validate_existing_store()?;
@@ -10084,13 +10085,31 @@ async fn prepare_mounted_session(
None,
),
}
.map(Arc::new)
.map(|runtime| {
(
Arc::new(runtime),
crate::tools::subagent::new_shared_subagent_manager_with_timeout(
worker_workspace,
MAX_SUBAGENTS,
MAX_SUBAGENTS,
Duration::from_secs(crate::config::DEFAULT_SUBAGENT_HEARTBEAT_TIMEOUT_SECS),
MAX_SUBAGENTS,
),
)
})
})
.await
.with_context(|| cli.operation_key.as_ref().map_or_else(
|| "inactive canonical owner could not be acquired; no history mutation attempted".to_string(),
|key| format!("retained operation {key}; existing owner could not be acquired; no mutation attempted"),
))?;
runtime
.attach_subagent_manager(workers)
.await
.with_context(|| cli.operation_key.as_ref().map_or_else(
|| "inactive canonical owner could not admit its worker authority; no history mutation attempted".to_string(),
|key| format!("retained operation {key}; worker authority could not be admitted; no mutation attempted"),
))?;
let binding = runtime.session_store_binding();
let operation_key = cli
.operation_key