Skip to content

Commit 6883351

Browse files
committed
fix(e2e): detect same-size log rotation
1 parent 93fd174 commit 6883351

4 files changed

Lines changed: 70 additions & 0 deletions

File tree

CHANGELOG.md

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

4646
### Fixes
4747

48+
- Release/CI/E2E: reset incremental log readers when watched log files rotate without shrinking, so same-size replacements do not hide new readiness or RPC lines.
4849
- Talk: preserve explicit `null` payloads on controller-created turn and output-audio lifecycle events.
4950
- Agents/TUI: keep local custom provider runs from loading plugin runtime and auth alias metadata when plugins are disabled.
5051
- Agents/TUI: restore in-flight TUI run switch-back behavior, keep no-policy native hook fallback available, guard vanished workspaces, and keep lightweight isolated subagents lightweight.

scripts/e2e/lib/incremental-line-reader.mjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,20 @@ function readSlice(filePath, start, length) {
1414
}
1515
}
1616

17+
function resolveFileIdentity(stats) {
18+
if (Number.isSafeInteger(stats.dev) && Number.isSafeInteger(stats.ino) && stats.ino !== 0) {
19+
return `${stats.dev}:${stats.ino}`;
20+
}
21+
return Number.isFinite(stats.birthtimeMs) ? `birth:${stats.birthtimeMs}` : undefined;
22+
}
23+
1724
export function resolvePositiveInteger(value, fallback) {
1825
return Number.isSafeInteger(value) && value > 0 ? value : fallback;
1926
}
2027

2128
export function createIncrementalLineReader(filePath, options = {}) {
2229
const maxReadBytes = resolvePositiveInteger(options.maxReadBytes, 256 * 1024);
30+
let fileIdentity;
2331
let offset = 0;
2432
let pending = "";
2533

@@ -35,6 +43,18 @@ export function createIncrementalLineReader(filePath, options = {}) {
3543
}
3644

3745
let reset = false;
46+
const nextFileIdentity = resolveFileIdentity(stats);
47+
if (
48+
fileIdentity !== undefined &&
49+
nextFileIdentity !== undefined &&
50+
fileIdentity !== nextFileIdentity
51+
) {
52+
offset = 0;
53+
pending = "";
54+
reset = true;
55+
}
56+
fileIdentity = nextFileIdentity;
57+
3858
if (stats.size < offset) {
3959
offset = 0;
4060
pending = "";

test/scripts/codex-media-path-client.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ function jsonl(value: unknown): string {
1919
return `${JSON.stringify(value)}\n`;
2020
}
2121

22+
function padJsonlToLength(value: Record<string, unknown>, length: number): string {
23+
const base = jsonl({ ...value, pad: "" });
24+
if (base.length > length) {
25+
throw new Error(`cannot pad JSONL down from ${base.length} to ${length}`);
26+
}
27+
return jsonl({ ...value, pad: "x".repeat(length - base.length) });
28+
}
29+
2230
function runWriteConfig(root: string, env: Record<string, string> = {}) {
2331
return spawnSync(process.execPath, [writeConfigPath], {
2432
encoding: "utf8",
@@ -132,4 +140,21 @@ describe("codex media path JSONL tailer", () => {
132140
writeFileSync(logPath, jsonl({ method: "turn/start" }));
133141
expect(tailer.read()).toEqual([{ method: "turn/start" }]);
134142
});
143+
144+
it("resets request history when a rotated app-server log keeps the same size", () => {
145+
const logPath = path.join(makeTempRoot(), "app-server.jsonl");
146+
const tailer = createJsonlRequestTailer(logPath, { maxReadBytes: 1024, historyLimit: 10 });
147+
const oldText = jsonl({ method: "initialize", pad: "x".repeat(64) });
148+
const replacementText = padJsonlToLength({ method: "turn/start" }, oldText.length);
149+
const replacement = JSON.parse(replacementText) as Record<string, unknown>;
150+
151+
expect(replacementText.length).toBe(oldText.length);
152+
writeFileSync(logPath, oldText);
153+
expect(tailer.read()).toEqual([{ method: "initialize", pad: "x".repeat(64) }]);
154+
155+
rmSync(logPath, { force: true });
156+
writeFileSync(logPath, replacementText);
157+
158+
expect(tailer.read()).toEqual([replacement]);
159+
});
135160
});

test/scripts/config-reload-log-scanner.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,28 @@ describe("config reload log scanner", () => {
9595
expect(result.reloadLines).toEqual([]);
9696
expect(result.restartLines).toEqual(["config change requires gateway restart: new.path"]);
9797
});
98+
99+
it("resets accumulated matches when a rotated log keeps the same size", () => {
100+
const logPath = path.join(makeTempRoot(), "gateway.log");
101+
const scanner = createConfigReloadLogScanner(logPath, {
102+
maxReadBytes: 1024,
103+
tailLineLimit: 4,
104+
});
105+
const oldText = "config change detected; evaluating reload: old.path with enough padding\n";
106+
const restartLine = "config change requires gateway restart: new.path";
107+
const restartText = `${restartLine}${" ".repeat(oldText.length - restartLine.length - 1)}\n`;
108+
109+
expect(Buffer.byteLength(restartText)).toBe(Buffer.byteLength(oldText));
110+
writeFileSync(logPath, oldText);
111+
expect(scanner.scan().reloadLines).toEqual([
112+
"config change detected; evaluating reload: old.path with enough padding",
113+
]);
114+
115+
rmSync(logPath, { force: true });
116+
writeFileSync(logPath, restartText);
117+
118+
const result = scanner.scan();
119+
expect(result.reloadLines).toEqual([]);
120+
expect(result.restartLines).toEqual([restartText.replace(/\n$/u, "")]);
121+
});
98122
});

0 commit comments

Comments
 (0)