Skip to content

Commit f7ed29e

Browse files
committed
fix: thread session write-lock timeout config
1 parent 731f640 commit f7ed29e

39 files changed

Lines changed: 258 additions & 20 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Docs: https://docs.openclaw.ai
4343

4444
### Fixes
4545

46+
- Sessions/transcripts: use one `session.writeLock.acquireTimeoutMs` policy for session transcript lock acquisitions and raise the default wait to 60 seconds, avoiding user-visible lock timeouts during legitimate slow prep, cleanup, compaction, and mirror work. Fixes #75894. Thanks @shandutta.
4647
- Control UI: contain the standalone iOS PWA viewport with safe-area-aware document locking, so Add-to-Home-Screen launches cannot scroll past the device bounds. Refs #76072. Thanks @kvncrw.
4748
- Agents/restart recovery: match cleaned transcript locks by exact transcript lock paths plus the canonical session fallback, so interrupted main sessions using topic-suffixed transcripts resume after gateway restart. Refs #76052. Thanks @anyech.
4849
- Agents/runtime: cache the stable system-prompt prefix and reuse prompt-report tool schema stats during dispatch prep, reducing repeated CPU work before streaming starts. Fixes #75999; supersedes #76061. Thanks @zackchiutw and @STLI69.

docs/concepts/agent-loop.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ wired end-to-end.
5050
See [Command Queue](/concepts/queue).
5151
- Transcript writes are also protected by a session write lock on the session file. The lock is
5252
process-aware and file-based, so it catches writers that bypass the in-process queue or come from
53-
another process.
53+
another process. Session transcript writers wait up to `session.writeLock.acquireTimeoutMs`
54+
before reporting the session as busy; the default is `60000` ms.
5455
- Session write locks are non-reentrant by default. If a helper intentionally nests acquisition of
5556
the same lock while preserving one logical writer, it must opt in explicitly with
5657
`allowReentrant: true`.

docs/reference/session-management-compaction.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ configured age, count, or disk budget.
9494

9595
OpenClaw no longer creates automatic `sessions.json.bak.*` rotation backups during Gateway writes. The legacy `session.maintenance.rotateBytes` key is ignored and `openclaw doctor --fix` removes it from older configs.
9696

97+
Transcript mutations use a session write lock on the transcript file. Lock acquisition waits up to
98+
`session.writeLock.acquireTimeoutMs` before surfacing a busy-session error; the default is `60000`
99+
ms. Raise this only when legitimate prep, cleanup, compaction, or transcript mirror work contends
100+
longer on slow machines. Stale-lock detection and maximum hold warnings remain separate policies.
101+
97102
Enforcement order for disk budget cleanup (`mode: "enforce"`):
98103

99104
1. Remove oldest archived, orphan transcript, or orphan trajectory artifacts first.

extensions/codex/src/app-server/compact.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export async function maybeCompactCodexAppServerSession(
7474
sessionFile: params.sessionFile,
7575
reason: "compaction",
7676
runtimeContext: params.contextEngineRuntimeContext,
77+
config: params.config,
7778
});
7879
} catch (error) {
7980
embeddedAgentLog.warn(

extensions/codex/src/app-server/run-attempt.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ export async function runCodexAppServerAttempt(
430430
tokenBudget: params.contextTokenBudget,
431431
}),
432432
runMaintenance: runHarnessContextEngineMaintenance,
433+
config: params.config,
433434
warn: (message) => embeddedAgentLog.warn(message),
434435
});
435436
historyMessages =
@@ -1178,6 +1179,7 @@ export async function runCodexAppServerAttempt(
11781179
promptCache: result.promptCache,
11791180
}),
11801181
runMaintenance: runHarnessContextEngineMaintenance,
1182+
config: params.config,
11811183
warn: (message) => embeddedAgentLog.warn(message),
11821184
});
11831185
}
@@ -1638,6 +1640,7 @@ async function mirrorTranscriptBestEffort(params: {
16381640
sessionKey: params.sessionKey,
16391641
messages: params.result.messagesSnapshot,
16401642
idempotencyScope: `codex-app-server:${params.threadId}:${params.turnId}`,
1643+
config: params.params.config,
16411644
});
16421645
} catch (error) {
16431646
embeddedAgentLog.warn("failed to mirror codex app-server transcript", { error });

extensions/codex/src/app-server/transcript-mirror.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import { CURRENT_SESSION_VERSION, type SessionManager } from "@mariozechner/pi-c
66
import {
77
acquireSessionWriteLock,
88
emitSessionTranscriptUpdate,
9+
resolveSessionWriteLockAcquireTimeoutMs,
910
runAgentHarnessBeforeMessageWriteHook,
1011
type AgentMessage,
12+
type SessionWriteLockAcquireTimeoutConfig,
1113
} from "openclaw/plugin-sdk/agent-harness-runtime";
1214

1315
const TRANSCRIPT_APPEND_SCAN_CHUNK_BYTES = 64 * 1024;
@@ -25,6 +27,7 @@ export async function mirrorCodexAppServerTranscript(params: {
2527
agentId?: string;
2628
messages: AgentMessage[];
2729
idempotencyScope?: string;
30+
config?: SessionWriteLockAcquireTimeoutConfig;
2831
}): Promise<void> {
2932
const messages = params.messages.filter(
3033
(message) => message.role === "user" || message.role === "assistant",
@@ -36,7 +39,7 @@ export async function mirrorCodexAppServerTranscript(params: {
3639
await fs.mkdir(path.dirname(params.sessionFile), { recursive: true });
3740
const lock = await acquireSessionWriteLock({
3841
sessionFile: params.sessionFile,
39-
timeoutMs: 10_000,
42+
timeoutMs: resolveSessionWriteLockAcquireTimeoutMs(params.config),
4043
});
4144
try {
4245
const existingIdempotencyKeys = await readTranscriptIdempotencyKeys(params.sessionFile);

src/agents/agent-command.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,7 @@ async function agentCommandInternal(
584584
sessionAgentId,
585585
threadId: opts.threadId,
586586
sessionCwd: resolveAcpSessionCwd(acpResolution.meta) ?? workspaceDir,
587+
config: cfg,
587588
});
588589
} catch (error) {
589590
log.warn(
@@ -1208,6 +1209,7 @@ async function agentCommandInternal(
12081209
sessionAgentId,
12091210
threadId: opts.threadId,
12101211
sessionCwd: workspaceDir,
1212+
config: cfg,
12111213
});
12121214
sessionEntry = await (
12131215
await loadCliCompactionRuntime()

src/agents/command/attempt-execution.cli.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ describe("CLI attempt execution", () => {
387387
storePath,
388388
sessionAgentId: "main",
389389
sessionCwd: tmpDir,
390+
config: {},
390391
});
391392

392393
const sessionFile = updatedEntry?.sessionFile;
@@ -443,6 +444,7 @@ describe("CLI attempt execution", () => {
443444
storePath,
444445
sessionAgentId: "main",
445446
sessionCwd: tmpDir,
447+
config: {},
446448
});
447449

448450
const messages = await readSessionMessages(updatedEntry?.sessionFile ?? "");

src/agents/command/attempt-execution.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ import { isCliRuntimeAlias, resolveCliRuntimeExecutionProvider } from "../model-
2121
import { isCliProvider } from "../model-selection.js";
2222
import { runEmbeddedPiAgent, type EmbeddedPiRunResult } from "../pi-embedded.js";
2323
import { buildAgentRuntimeAuthPlan } from "../runtime-plan/auth.js";
24-
import { acquireSessionWriteLock } from "../session-write-lock.js";
24+
import {
25+
acquireSessionWriteLock,
26+
resolveSessionWriteLockAcquireTimeoutMs,
27+
} from "../session-write-lock.js";
2528
import { buildWorkspaceSkillSnapshot } from "../skills.js";
2629
import { buildUsageWithNoCost } from "../stream-message-shared.js";
2730
import {
@@ -76,6 +79,7 @@ type PersistTextTurnTranscriptParams = {
7679
sessionAgentId: string;
7780
threadId?: string | number;
7881
sessionCwd: string;
82+
config: OpenClawConfig;
7983
assistant: {
8084
api: string;
8185
provider: string;
@@ -193,7 +197,7 @@ async function persistTextTurnTranscript(
193197
});
194198
const lock = await acquireSessionWriteLock({
195199
sessionFile,
196-
timeoutMs: 10_000,
200+
timeoutMs: resolveSessionWriteLockAcquireTimeoutMs(params.config),
197201
allowReentrant: true,
198202
});
199203
try {
@@ -202,6 +206,7 @@ async function persistTextTurnTranscript(
202206
transcriptPath: sessionFile,
203207
sessionId: params.sessionId,
204208
cwd: params.sessionCwd,
209+
config: params.config,
205210
message: {
206211
role: "user",
207212
content: promptText,
@@ -215,6 +220,7 @@ async function persistTextTurnTranscript(
215220
transcriptPath: sessionFile,
216221
sessionId: params.sessionId,
217222
cwd: params.sessionCwd,
223+
config: params.config,
218224
message: {
219225
role: "assistant",
220226
content: [{ type: "text", text: replyText }],
@@ -264,6 +270,7 @@ export async function persistAcpTurnTranscript(params: {
264270
sessionAgentId: string;
265271
threadId?: string | number;
266272
sessionCwd: string;
273+
config: OpenClawConfig;
267274
}): Promise<SessionEntry | undefined> {
268275
return await persistTextTurnTranscript({
269276
...params,
@@ -287,6 +294,7 @@ export async function persistCliTurnTranscript(params: {
287294
sessionAgentId: string;
288295
threadId?: string | number;
289296
sessionCwd: string;
297+
config: OpenClawConfig;
290298
}): Promise<SessionEntry | undefined> {
291299
const replyText = resolveCliTranscriptReplyText(params.result);
292300
const provider = params.result.meta.agentMeta?.provider?.trim() ?? "cli";
@@ -304,6 +312,7 @@ export async function persistCliTurnTranscript(params: {
304312
sessionAgentId: params.sessionAgentId,
305313
threadId: params.threadId,
306314
sessionCwd: params.sessionCwd,
315+
config: params.config,
307316
assistant: {
308317
api: "cli",
309318
provider,

src/agents/command/cli-compaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ async function compactCliTranscript(params: {
166166
reason: "compaction",
167167
sessionManager: params.sessionManager,
168168
runtimeContext,
169+
config: params.cfg,
169170
});
170171
return true;
171172
}

0 commit comments

Comments
 (0)