Skip to content

fix(agents): keep continuation bootstrap marker across short reads#108253

Merged
steipete merged 2 commits into
openclaw:mainfrom
sunlit-deng:sunlit/fix/bootstrap-marker-short-read
Jul 15, 2026
Merged

fix(agents): keep continuation bootstrap marker across short reads#108253
steipete merged 2 commits into
openclaw:mainfrom
sunlit-deng:sunlit/fix/bootstrap-marker-short-read

Conversation

@sunlit-deng

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes an issue where continuation sessions could re-inject full bootstrap context when the transcript tail read returned short before the full-bootstrap completion marker. On filesystems that allow short positional reads, continuation-skip could misread an existing completed turn as missing and restart the heavier bootstrap path.

Why This Change Was Made

The continuation marker scanner now fills its bounded tail window until EOF or the requested window is read, preserving the existing scan size and compaction semantics.

User Impact

Continuation runs keep their intended lightweight bootstrap behavior even when the transcript file read completes in multiple chunks.

Evidence

  • node scripts/run-vitest.mjs src/agents/bootstrap-files.test.ts: 40 passed.
  • git diff --check: passed.
  • Real production-module proof with FileHandle.read capped at 16 bytes per call:
Baseline on origin/main (bb3fae834810cf466be70668b1bce2bd16e31227):
entrypoint: hasCompletedBootstrapTurn
read-boundary: fs.open FileHandle.read capped at 16 bytes per call
marker-detected: false
compaction-negative-control: false

After this branch:
entrypoint: hasCompletedBootstrapTurn
read-boundary: fs.open FileHandle.read capped at 16 bytes per call
marker-detected: true
compaction-negative-control: false
live-proof.mjs
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";

const repoRoot = process.cwd();
const { FULL_BOOTSTRAP_COMPLETED_CUSTOM_TYPE, hasCompletedBootstrapTurn } = await import(
  pathToFileURL(path.join(repoRoot, "src/agents/bootstrap-files.ts")).href
);

const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-bootstrap-marker-proof-"));
const realOpen = fs.open.bind(fs);

fs.open = async (...args) => {
  const handle = await realOpen(...args);
  const realRead = handle.read.bind(handle);
  return new Proxy(handle, {
    get(target, prop, receiver) {
      if (prop === "read") {
        return (buffer, offset, length, position) =>
          realRead(buffer, offset, Math.min(length, 16), position);
      }
      return Reflect.get(target, prop, receiver);
    },
  });
};

async function writeTranscript(name, records) {
  const sessionFile = path.join(tmpDir, `${name}.jsonl`);
  await fs.writeFile(sessionFile, `${records.map((record) => JSON.stringify(record)).join("\n")}\n`);
  return sessionFile;
}

try {
  const markerFile = await writeTranscript("marker", [
    { type: "message", message: { role: "user", content: "hello" } },
    { type: "message", message: { role: "assistant", content: "hi" } },
    {
      type: "custom",
      customType: FULL_BOOTSTRAP_COMPLETED_CUSTOM_TYPE,
      data: { timestamp: 1 },
    },
  ]);
  const compactedFile = await writeTranscript("compacted", [
    {
      type: "custom",
      customType: FULL_BOOTSTRAP_COMPLETED_CUSTOM_TYPE,
      data: { timestamp: 1 },
    },
    { type: "compaction", summary: "trimmed" },
  ]);

  console.log(`entrypoint: hasCompletedBootstrapTurn`);
  console.log(`read-boundary: fs.open FileHandle.read capped at 16 bytes per call`);
  console.log(`marker-detected: ${await hasCompletedBootstrapTurn(markerFile)}`);
  console.log(`compaction-negative-control: ${await hasCompletedBootstrapTurn(compactedFile)}`);
} finally {
  fs.open = realOpen;
  await fs.rm(tmpDir, { recursive: true, force: true });
}

AI-assisted: built with Codex

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Jul 15, 2026
@sunlit-deng
sunlit-deng force-pushed the sunlit/fix/bootstrap-marker-short-read branch from c9b6ce0 to 880d679 Compare July 15, 2026 15:13
@steipete steipete self-assigned this Jul 15, 2026
@openclaw-barnacle openclaw-barnacle Bot added the commands Command implementations label Jul 15, 2026
@steipete
steipete merged commit 8cc8fee into openclaw:main Jul 15, 2026
119 of 120 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 16, 2026
…penclaw#108253)

* fix(agents): keep bootstrap markers across short reads

* refactor(infra): share bounded file window reads

Co-authored-by: sunlit-deng <[email protected]>

---------

Co-authored-by: Peter Steinberger <[email protected]>
steipete pushed a commit to Monkey-wusky/openclaw that referenced this pull request Jul 16, 2026
Replace single-shot handle.read() calls in two session tail-read
paths with readFileWindowFully so multibyte-range positional reads
do not silently return incomplete tail data on network filesystems.

- readRecentTranscriptTailLinesAsync: tail-window read for recent
  session messages now loops until the requested window fills
- readLastMessagePreviewFromOpenTranscriptAsync: last-message
  preview tail read now loops until the requested window fills

The same readFileWindowFully helper was introduced in openclaw#108253 and
expanded with a sync variant in openclaw#108127 (both by sunlit-deng).
steipete pushed a commit to Monkey-wusky/openclaw that referenced this pull request Jul 16, 2026
Replace single-shot handle.read() calls in two session tail-read
paths with readFileWindowFully so multibyte-range positional reads
do not silently return incomplete tail data on network filesystems.

- readRecentTranscriptTailLinesAsync: tail-window read for recent
  session messages now loops until the requested window fills
- readLastMessagePreviewFromOpenTranscriptAsync: last-message
  preview tail read now loops until the requested window fills

The same readFileWindowFully helper was introduced in openclaw#108253 and
expanded with a sync variant in openclaw#108127 (both by sunlit-deng).
steipete pushed a commit to Monkey-wusky/openclaw that referenced this pull request Jul 16, 2026
Replace single-shot handle.read() calls in two session tail-read
paths with readFileWindowFully so multibyte-range positional reads
do not silently return incomplete tail data on network filesystems.

- readRecentTranscriptTailLinesAsync: tail-window read for recent
  session messages now loops until the requested window fills
- readLastMessagePreviewFromOpenTranscriptAsync: last-message
  preview tail read now loops until the requested window fills

The same readFileWindowFully helper was introduced in openclaw#108253 and
expanded with a sync variant in openclaw#108127 (both by sunlit-deng).
steipete added a commit that referenced this pull request Jul 16, 2026
…ads (#108655)

* fix(sessions): complete tail-read windows despite short positional reads

Replace single-shot handle.read() calls in two session tail-read
paths with readFileWindowFully so multibyte-range positional reads
do not silently return incomplete tail data on network filesystems.

- readRecentTranscriptTailLinesAsync: tail-window read for recent
  session messages now loops until the requested window fills
- readLastMessagePreviewFromOpenTranscriptAsync: last-message
  preview tail read now loops until the requested window fills

The same readFileWindowFully helper was introduced in #108253 and
expanded with a sync variant in #108127 (both by sunlit-deng).

* fix(sessions): complete sync title preview reads

---------

Co-authored-by: Peter Steinberger <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 17, 2026
…ads (openclaw#108655)

* fix(sessions): complete tail-read windows despite short positional reads

Replace single-shot handle.read() calls in two session tail-read
paths with readFileWindowFully so multibyte-range positional reads
do not silently return incomplete tail data on network filesystems.

- readRecentTranscriptTailLinesAsync: tail-window read for recent
  session messages now loops until the requested window fills
- readLastMessagePreviewFromOpenTranscriptAsync: last-message
  preview tail read now loops until the requested window fills

The same readFileWindowFully helper was introduced in openclaw#108253 and
expanded with a sync variant in openclaw#108127 (both by sunlit-deng).

* fix(sessions): complete sync title preview reads

---------

Co-authored-by: Peter Steinberger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling commands Command implementations size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants