Skip to content

Agents: default context injection to continuation-skip#67447

Open
neeravmakwana wants to merge 2 commits intoopenclaw:mainfrom
neeravmakwana:fix/context-injection-default-continuation-skip-67419
Open

Agents: default context injection to continuation-skip#67447
neeravmakwana wants to merge 2 commits intoopenclaw:mainfrom
neeravmakwana:fix/context-injection-default-continuation-skip-67419

Conversation

@neeravmakwana
Copy link
Copy Markdown
Contributor

Summary

  • Problem: workspace bootstrap files were reinjected on every follow-up turn by default because agents.defaults.contextInjection implicitly resolved to \"always\".
  • Why it matters: this duplicates stable bootstrap context across multi-turn sessions and increases prompt token overhead.
  • What changed: switched the default mode to \"continuation-skip\", updated config/help/docs text to match, and refreshed the config baseline hash.
  • What did NOT change (scope boundary): opt-in \"always\" behavior still exists unchanged, and continuation safety checks (heartbeat/compaction marker handling) remain the same.

Change Type

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause

  • Root cause: runtime default in resolveContextInjectionMode() was \"always\", so bootstrap context was reinjected unless users explicitly opted into \"continuation-skip\".
  • Missing detection / guardrail: existing tests covered continuation-skip behavior but also encoded always as the default.
  • Contributing context (if known): the continuation-skip path existed and was validated, but remained opt-in.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • src/agents/bootstrap-files.test.ts (resolveContextInjectionMode defaults)
    • src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts
    • src/agents/pi-embedded-runner/run/attempt.spawn-workspace.bootstrap-marker.test.ts
  • Scenario the test should lock in: default config path uses continuation-skip while preserving explicit always override behavior.
  • Why this is the smallest reliable guardrail: it exercises the exact default-resolution helper and the closest attempt-layer continuation behavior.
  • Existing test that already covers this (if any): continuation-skip turn gating tests already exist in the attempt suite.
  • If no new test is added, why not: existing targeted tests were updated and rerun for the default switch.

User-visible / Behavior Changes

  • Default agents.defaults.contextInjection is now \"continuation-skip\".
  • Operators can still force per-turn bootstrap injection via agents.defaults.contextInjection: \"always\".

Diagram

Before:
[follow-up turn] -> [default always] -> [bootstrap reinjected]

After:
[follow-up turn] -> [default continuation-skip + completed turn marker] -> [bootstrap skipped]

Security Impact

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS (darwin 25.3.0)
  • Runtime/container: local Node/pnpm
  • Model/provider: N/A (config/runtime path)
  • Integration/channel (if any): N/A
  • Relevant config (redacted): default agents.defaults.contextInjection path and explicit continuation-skip path

Steps

  1. Run pnpm test src/agents/bootstrap-files.test.ts -t \"resolveContextInjectionMode\".
  2. Run pnpm test src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts src/agents/pi-embedded-runner/run/attempt.spawn-workspace.bootstrap-marker.test.ts.
  3. Run pnpm config:docs:gen.

Expected

  • Default context injection resolves to continuation-skip.
  • Attempt-layer continuation tests continue to pass.

Actual

  • All targeted tests passed and config baseline hash updated.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification

  • Verified scenarios:
    • Default mode now resolves to continuation-skip.
    • Explicit continuation-skip behavior remains intact on safe continuation turns.
    • Docs/help/default wording now matches runtime behavior.
  • Edge cases checked:
    • Heartbeat/non-heartbeat and marker persistence paths remained unchanged (existing tests pass).
  • What you did not verify:
    • Full pnpm test and pnpm build lanes were not run for this focused change.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (Yes)
  • Migration needed? (No)
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: operators depending on implicit per-turn reinjection may observe behavior change under defaults.
    • Mitigation: explicit opt-in agents.defaults.contextInjection: \"always\" remains available and documented.

Additional Notes

  • AI-assisted implementation.

Made with Cursor

@openclaw-barnacle openclaw-barnacle bot added docs Improvements or additions to documentation gateway Gateway runtime agents Agent runtime and tooling size: XS labels Apr 16, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 16, 2026

Greptile Summary

Switches resolveContextInjectionMode()'s fallback from "always" to "continuation-skip", so follow-up turns in multi-turn sessions no longer re-inject workspace bootstrap files by default. The runtime change is a one-liner, and all affected surfaces (JSDoc, help text, generated schema, changelog, tests) are updated consistently. The opt-in "always" path and all continuation-safety checks are preserved unchanged.

Confidence Score: 5/5

Safe to merge — the one-line default change is well-contained, every affected surface is updated, and both modes remain valid.

All findings are P2 (a stale test-helper default that no longer reflects production semantics). No logic, data-integrity, or security issues found. Runtime behavior is correct and backed by updated unit tests.

attempt.spawn-workspace.context-injection.test.ts — the local helper still defaults to "always"; low priority but worth aligning.

Comments Outside Diff (1)

  1. src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts, line 30 (link)

    P2 Stale test helper default

    The local helper still falls back to "always" when no contextInjectionMode is given, which no longer matches the production default. Future test authors using this helper without an explicit mode will unknowingly exercise the non-default path, and the gap between the helper and production semantics can mask unintended regressions.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts
    Line: 30
    
    Comment:
    **Stale test helper default**
    
    The local helper still falls back to `"always"` when no `contextInjectionMode` is given, which no longer matches the production default. Future test authors using this helper without an explicit mode will unknowingly exercise the non-default path, and the gap between the helper and production semantics can mask unintended regressions.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts
Line: 30

Comment:
**Stale test helper default**

The local helper still falls back to `"always"` when no `contextInjectionMode` is given, which no longer matches the production default. Future test authors using this helper without an explicit mode will unknowingly exercise the non-default path, and the gap between the helper and production semantics can mask unintended regressions.

```suggestion
    contextInjectionMode: params.contextInjectionMode ?? "continuation-skip",
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Agents: default context injection to con..." | Re-trigger Greptile

@neeravmakwana
Copy link
Copy Markdown
Contributor Author

Addressed all current AI review feedback in one follow-up.

What was raised

  • Greptile flagged one P2: resolveBootstrapContext helper in attempt.spawn-workspace.context-injection.test.ts still defaulted contextInjectionMode to "always", which no longer matched production default semantics.

What I changed

  • Updated the helper default to "continuation-skip" so test defaults align with runtime behavior:
    • src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts

Validation run

  • pnpm test src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts
  • pnpm test src/agents/bootstrap-files.test.ts -t "resolveContextInjectionMode"
  • Commit hook check gate also passed (pnpm check).

No additional AI review findings remained actionable after this fix.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Apr 16, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Workspace bootstrap guardrails may be skipped by default on continuation turns (loss of system-prompt policies across restarts/compaction gaps)
1. 🟡 Workspace bootstrap guardrails may be skipped by default on continuation turns (loss of system-prompt policies across restarts/compaction gaps)
Property Value
Severity Medium
CWE CWE-657
Location src/agents/pi-embedded-runner/run/attempt.context-engine-helpers.ts:31-43

Description

The default agents.defaults.contextInjection mode was changed to "continuation-skip", which causes workspace bootstrap files (e.g., AGENTS.md, SOUL.md) to be omitted from the system prompt on subsequent turns after a prior “full bootstrap completed” marker is found in the session JSONL.

This is a security regression because those bootstrap files commonly contain repo-/workspace-specific safety constraints, tool restrictions, and operational policies. When they are not re-injected, the model can operate without seeing those guardrails.

Key issue in the current logic:

  • hasCompletedBootstrapTurn() only checks for a persisted marker (openclaw:bootstrap-context:full) and whether a compaction record appears after it.
  • On a later run (including after process restart / provider cache miss / prompt cache invalidation), the code may still treat the turn as a “safe continuation” and skip injecting bootstrap context.
  • The session transcript marker does not guarantee the current model request still includes the prior system-prompt bootstrap content (system prompts are not reliably persisted in the JSONL transcript).

Vulnerable flow (default config path):

  • Input/trigger: any follow-up user message in an existing session whose JSONL contains a prior bootstrap completion marker
  • Decision: resolveAttemptBootstrapContext() treats it as a continuation turn and returns empty contextFiles
  • Sink/impact: buildEmbeddedSystemPrompt({ contextFiles }) builds a system prompt without workspace bootstrap guardrails, enabling prompt-injection/policy-bypass relative to the intended workspace policies

Vulnerable code excerpts:

// defaults now skip re-injection
export function resolveContextInjectionMode(config?: OpenClawConfig): AgentContextInjection {
  return config?.agents?.defaults?.contextInjection ?? "continuation-skip";
}
const isContinuationTurn =
  params.contextInjectionMode === "continuation-skip" &&
  params.bootstrapContextRunKind !== "heartbeat" &&
  (await params.hasCompletedBootstrapTurn(params.sessionFile));

const context = isContinuationTurn
  ? ({ bootstrapFiles: [], contextFiles: [] } as unknown as TContext)
  : await params.resolveBootstrapContextForRun();

This can allow an attacker/user to exploit the absence of workspace-defined restrictions on later turns (especially after restarts or any situation where prior system prompt is not preserved) to induce unsafe tool use or bypass intended project policies.

Recommendation

Do not skip bootstrap injection unless you can prove the current model request still contains the bootstrap guardrails.

Safer options:

  1. Revert the default to "always", requiring an explicit opt-in to "continuation-skip".

  2. If keeping "continuation-skip", gate skipping on a stronger invariant than a session marker, e.g.:

    • only skip when the provider/session supports a verified prompt cache mechanism and the cache key indicates the same system prompt is still active, and
    • no restart/cache-break event occurred.
  3. Alternatively, persist a compact representation (hash) of injected bootstrap content into the session and, on each turn, re-inject whenever the active system prompt does not match that hash.

Example (conservative fix: default to always):

export function resolveContextInjectionMode(config?: OpenClawConfig): AgentContextInjection {
  return config?.agents?.defaults?.contextInjection ?? "always";
}

Example (additional guard before skipping):

const canSkip = providerSupportsVerifiedPromptCache && promptCacheStillValid;
const isContinuationTurn =
  params.contextInjectionMode === "continuation-skip" &&
  canSkip &&
  params.bootstrapContextRunKind !== "heartbeat" &&
  (await params.hasCompletedBootstrapTurn(params.sessionFile));

Analyzed PR: #67447 at commit f960c7e

Last updated on: 2026-04-16T00:48:14Z

@neeravmakwana
Copy link
Copy Markdown
Contributor Author

Thanks — I reviewed the Aisle report in detail.

I am not making an additional code change for this one because the finding treats workspace bootstrap text as a hard security boundary, but in OpenClaw the hard security controls are enforced in runtime policy/tooling paths, not by continued prompt reinjection.

Why this is not a security regression in this PR

  • This PR changes only the default for agents.defaults.contextInjection to continuation-skip.
  • The continuation path still has existing safety gates:
    • skip is disabled for heartbeat runs
    • skip is disabled until a successful full-bootstrap marker is persisted
    • skip is invalidated after compaction records
  • Tool execution and access control guardrails remain runtime-enforced and unchanged by this PR (allowlists/policy gates/sandbox/fs-safe surfaces), so they do not depend on bootstrap prompt text being repeated every turn.

Scope clarification

  • AGENTS.md/SOUL.md/workspace bootstrap files are instruction/context surfaces.
  • They are important for behavior quality and repo conventions, but they are not the only or primary enforcement layer for runtime security controls.

Operational fallback

If an operator wants strict per-turn reinjection, they can explicitly set:

{
  agents: { defaults: { contextInjection: "always" } }
}

Given the above, I’m keeping this PR focused as-is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling docs Improvements or additions to documentation gateway Gateway runtime size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Session context bloat: bootstrap files re-injected every turn, wasting 20-30% tokens

1 participant