Skip to content

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

@davidjoshua7692-code

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions