Skip to content

[Bug]: contextInjection "always" mode doesn't reset bootstrapFiles between turns — hooks accumulate entries every turn #80548

Description

@davidjoshua7692-code

Bug type

Behavior bug

Beta release blocker

No

Summary

With contextInjection: "always", the bootstrapFiles array is not reset between turns. Hooks that inject files into bootstrapFiles on the agent:bootstrap event cause the array to grow cumulatively — one duplicate per turn — bloating the system prompt, polluting instructions, and breaking LLM provider prompt cache on every turn.

Steps to reproduce

  1. Enable contextInjection: "always" (or leave default)
  2. Install or register an agent:bootstrap hook that pushes a virtual file to event.context.bootstrapFiles
  3. Example hook (~/.openclaw/hooks/example/handler.js):
    const handler = async (event) => {
      if (event.type !== 'agent' || event.action !== 'bootstrap') return;
      if (Array.isArray(event.context.bootstrapFiles)) {
        event.context.bootstrapFiles.push({
          path: 'EXAMPLE_VIRTUAL.md',
          content: 'Example content',
          virtual: true,
        });
      }
    };
    module.exports = handler;
  4. Enable diagnostics.cacheTrace in openclaw.json and restart
  5. Have a multi-turn conversation (4+ turns)
  6. Check cache-trace logs: each turn's system prompt grows by one copy of the virtual file

Expected behavior

Expected behavior

In contextInjection: "always" mode, each turn's agent:bootstrap event should receive a fresh bootstrapFiles array (only containing the current turn's resolved workspace files). Hooks can safely push virtual files without worrying about accumulation.

Actual behavior

Actual behavior

bootstrapFiles array grows cumulatively — Turn N contains N copies of the hook's virtual file. This causes:

  • System prompt prefix grows ~19 lines (706 bytes) per turn per virtual-file hook
  • LLM provider prompt cache prefix mismatch on every turn (cache miss)
  • Wasted tokens proportional to conversation length

OpenClaw version

OpenClaw version

2026.4.15

Operating system

Operating system

Ubuntu 24.04 on WSL2 (Linux 6.6.87.2-microsoft-standard-WSL2 x64)

Install method

Install method

npm global

Model

Model

zai/glm-5-turbo (also reproducible with any model — it's a framework-level bug)

Provider / routing chain

Provider / routing chain

openclaw -> zai (智谱 GLM API)

Logs, screenshots, and evidence

Logs, screenshots, and evidence

Debug method: cache trace + prefix diff

Enabled diagnostics.cacheTrace with includeSystem: true, collected 4 turns of data, then compared system prompt prefixes before <!-- OPENCLAW_CACHE_BOUNDARY -->.

import json
turns = []
with open('cache-trace-frozen.jsonl') as f:
    for line in f:
        r = json.loads(line)
        if r.get('stage') == 'session:loaded':
            turns.append(r['system'])

for i, t in enumerate(turns):
    pre = t.split('<!-- OPENCLAW_CACHE_BOUNDARY -->')[0]
    count = pre.count('SELF_IMPROVEMENT')
    print(f'Turn {i+1}: prefix={len(pre)} chars, {len(pre.split(chr(10)))} lines, virtual file ×{count}')

Results

Turn Prefix chars Prefix lines Virtual file copies
1 28,108 629 1
2 28,814 648 2
3 29,520 667 3
4 30,226 686 4
  • First 618 lines are identical across all 4 turns ✅
  • Difference starts at line 619: each turn inserts one more copy of the virtual file before ## Silent Replies
  • Each copy is ~19 lines / 706 bytes, inserted at a fixed offset from the previous copy

System prompt structure (simplified)

L1~L599:  Stable prefix (tools, skills, workspace files) — identical all turns ✅
L600:     SELF_IMPROVEMENT_REMINDER.md (copy 1) ← hook virtual file
L619:     SELF_IMPROVEMENT_REMINDER.md (copy 2) ← Turn 2+
L638:     SELF_IMPROVEMENT_REMINDER.md (copy 3) ← Turn 3+
L657:     SELF_IMPROVEMENT_REMINDER.md (copy 4) ← Turn 4+
          ## Silent Replies
          <!-- OPENCLAW_CACHE_BOUNDARY -->  ← shifts right each turn
          Dynamic content (unchanged)

Hook source that triggered the issue

~/.openclaw/hooks/self-improving-agent/handler.js — the self-improving-agent bundled hook pushes a SELF_IMPROVEMENT_REMINDER.md virtual file on every agent:bootstrap event. With always mode, bootstrap fires every turn, and bootstrapFiles is not reset, causing the accumulation.

Impact and severity

Impact and severity

  • Affected: All users with contextInjection: "always" (default) who have any agent:bootstrap hook that injects files into bootstrapFiles
  • Severity: Medium — does not crash but silently degrades every multi-turn session
  • Frequency: Always reproducible (4/4 turns)
  • Consequence:
    • Prompt cache breakage: LLM provider prompt cache is invalidated every turn because the prefix changes, eliminating cache read savings entirely
    • Prompt pollution: Duplicate instructions accumulate in the system prompt (1→2→3→4 copies...). The model receives increasingly redundant context, which may confuse instruction-following and dilute other system prompt sections
    • Token waste: Each turn adds ~706 tokens per hook-injected file (scales with conversation length)
    • In a 20-turn conversation with one hook: 20 consecutive cache misses, 20 copies of the same instruction polluting the prompt, and ~14KB wasted prefix

Additional information

Additional information

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions