Skip to content

fix(cli-runner): fire before_agent_reply on cron-triggered turns#70950

Merged
Patrick-Erichsen merged 2 commits into
openclaw:mainfrom
Patrick-Erichsen:patrick/cli-runner-before-agent-reply-hook
Apr 24, 2026
Merged

fix(cli-runner): fire before_agent_reply on cron-triggered turns#70950
Patrick-Erichsen merged 2 commits into
openclaw:mainfrom
Patrick-Erichsen:patrick/cli-runner-before-agent-reply-hook

Conversation

@Patrick-Erichsen

@Patrick-Erichsen Patrick-Erichsen commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a before_agent_reply call site to runPreparedCliAgent so plugin-managed cron jobs can short-circuit a CLI-backed agent turn before the codex/claude/gemini subprocess is spawned. Mirrors the embedded PI runner gate at src/agents/pi-embedded-runner/run.ts:326.

Without this, configuring a default agent's model to a CLI backend silently broke memory-core dreaming: the cron sentinel was sent to the underlying LLM as a literal user prompt and the dreaming hook never executed.

Closes #70940.

TDD trail

  • 8998e9b5c6 — RED: 4-case test mirroring pi-embedded-runner/run.before-agent-reply-cron.test.ts. Two cases fail without the gate; two pass trivially.
  • 9dc1cfbfb6 — GREEN: hook gate added to runPreparedCliAgent. 4/4 pass.

Empirical validation (manual e2e)

Reran the same isolated-gateway repro from #70940 against this branch (OPENCLAW_HOME=/tmp/dream-test-cli, model: { primary: "codex-cli/gpt-5.5" }, dreaming enabled at */2 * * * *).

Signal Pre-fix (#70940 repro) This branch
memory-core: dreaming promotion complete lines 0 in 5 min 3 (manual + 2 natural firings)
Dream-sentinel sent to LLM as literal prompt Yes (promptChars=217) No
Hook short-circuit duration (natural firings) n/a 140ms / 148ms

The fast natural firings are the unambiguous proof: hook fires, returns handled: true, cli subprocess is skipped entirely.

Test plan

  • pnpm test src/agents/cli-runner.before-agent-reply-cron.test.ts — 4/4
  • pnpm test src/agents/cli-runner — 21/21
  • Lint clean on touched files
  • Manual e2e against codex-cli backend (above)

Note

buildHandledReplyPayloads is now duplicated in cli-runner.ts and pi-embedded-runner/run.ts. Worth a follow-up to extract; out of scope here to keep the diff tight.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation gateway Gateway runtime extensions: memory-core Extension: memory-core commands Command implementations agents Agent runtime and tooling size: XL maintainer Maintainer-authored PR labels Apr 24, 2026
Mirrors src/agents/pi-embedded-runner/run.before-agent-reply-cron.test.ts
for the CLI runner. Asserts:

1. When trigger=cron and a before_agent_reply hook claims the turn
   (handled: true), runCliAgent must NOT invoke the codex subprocess and
   must return the hook's reply text in payloads[0].
2. When the hook claims without a reply body, the synthesized payload
   uses SILENT_REPLY_TOKEN.
3. Non-cron triggers do not invoke the hook (no behavior change for
   normal user/heartbeat traffic).
4. Without a registered hook, falls through to the CLI subprocess.

Currently fails (RED): tests 1 and 2 fail because runCliAgent never
fires before_agent_reply — the hook gate exists only in the embedded PI
runner (src/agents/pi-embedded-runner/run.ts:326). This is the
CLI-backed-agent dreaming gap reported in openclaw#70940 and identified in
PR openclaw#70737 review.

Next commit: implement the hook gate in runPreparedCliAgent (GREEN).
@Patrick-Erichsen
Patrick-Erichsen force-pushed the patrick/cli-runner-before-agent-reply-hook branch from 8be920b to 6d2c3da Compare April 24, 2026 05:25
@Patrick-Erichsen
Patrick-Erichsen marked this pull request as ready for review April 24, 2026 05:25
@openclaw-barnacle openclaw-barnacle Bot added size: S and removed docs Improvements or additions to documentation gateway Gateway runtime extensions: memory-core Extension: memory-core commands Command implementations size: XL labels Apr 24, 2026
@greptile-apps

greptile-apps Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR ports the cron-triggered before_agent_reply gate from the embedded PI runner into runPreparedCliAgent, preventing the dreaming sentinel from being forwarded to the underlying CLI subprocess as a literal user prompt. The implementation and test coverage are structurally sound and match the embedded runner contract.

  • Resource leak on cron short-circuit: Unlike the embedded PI runner — where the cron gate fires before any resource allocation — runPreparedCliAgent already holds resources prepared by prepareCliRunContext (MCP bundle config temp files, preparedExecution.cleanup) when the early return fires. The try…finally cleanup at the bottom is never reached, so every dreaming cron turn handled by the hook leaks those resources.

Confidence Score: 3/5

Mergeable in principle, but the cron gate early-return skips backend cleanup and will leak MCP config resources on each handled dreaming turn.

The functional fix is correct and the tests are thorough. However, the gate placement inside runPreparedCliAgent — after prepareCliRunContext has already allocated backend resources — means cleanup is skipped every time the hook handles a cron turn. This is a present, deterministic resource leak (not speculative), triggered on the exact path this PR is meant to enable.

src/agents/cli-runner.ts — specifically the placement of the cron gate relative to resource allocation and the try…finally cleanup block.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/cli-runner.ts
Line: 117-137

Comment:
**Cron gate skips `preparedBackend.cleanup`, leaking resources**

`prepareCliRunContext` may allocate real resources — MCP bundle config temp files and/or backend execution cleanup closures via `backendResolved.prepareExecution` — that are meant to be released in the `try…finally` block at the bottom of `runPreparedCliAgent`. The early return here exits before that block, so every cron turn the hook handles will silently skip cleanup.

The embedded PI runner's equivalent gate (`run.ts:334`) fires before any resource setup, so it doesn't have this problem. The safest fix is to hoist this check into `runCliAgent` before `prepareCliRunContext` is called, or to call `await context.preparedBackend.cleanup?.()` in a `try…finally` wrapping the early return.

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

Reviews (1): Last reviewed commit: "fix(cli-runner): GREEN — fire before_age..." | Re-trigger Greptile

Comment thread src/agents/cli-runner.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d2c3daa1d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/cli-runner.ts Outdated
@Patrick-Erichsen
Patrick-Erichsen force-pushed the patrick/cli-runner-before-agent-reply-hook branch 2 times, most recently from 9dc1cfb to f0d11be Compare April 24, 2026 05:49
…urns

Mirrors the embedded PI runner gate from
src/agents/pi-embedded-runner/run.ts:326 so plugin-managed cron jobs
(notably memory-core dreaming) can short-circuit a CLI-backed agent
turn before the codex/claude/gemini subprocess is spawned.

Without this, configuring a default agent's model to a CLI backend
(codex-cli, claude-cli, gemini-cli, or any third-party
`registerCliBackend` provider) silently broke dreaming: the cron
sentinel was sent to the underlying LLM as a literal user prompt and
the dreaming hook never executed. See openclaw#70940 for the
empirical repro (codex-cli observed sending the dream-token to GPT-5.5
with no `memory-core: dreaming promotion complete` line).

Also extracts `buildHandledReplyPayloads` locally; eventually that
should be unified with the embedded PI runner's helper, but that's a
mechanical refactor for a follow-up.

Closes openclaw#70940 once both this PR and openclaw#70737 land — this fix is only
useful if cron-driven dreaming exists, which is what openclaw#70737 introduces.

TDD trail:
- prior commit: RED test asserting the hook gate (4 cases)
- this commit: implementation that turns those tests green (4/4 pass).

Verified: pnpm test src/agents/cli-runner.before-agent-reply-cron.test.ts
4/4 passed; pnpm test src/agents/cli-runner 21/21 passed; lint clean
on touched files; pre-existing tsgo failure in
src/plugin-sdk/provider-tools.ts is unrelated to these changes.
@Patrick-Erichsen
Patrick-Erichsen force-pushed the patrick/cli-runner-before-agent-reply-hook branch from f0d11be to 2eeeb4b Compare April 24, 2026 05:56
@aisle-research-bot

aisle-research-bot Bot commented Apr 24, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

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

# Severity Title
1 🟡 Medium Sensitive sessionKey and filesystem path exposed to before_agent_reply plugin hooks (cron gate)
1. 🟡 Sensitive sessionKey and filesystem path exposed to before_agent_reply plugin hooks (cron gate)
Property Value
Severity Medium
CWE CWE-200
Location src/agents/cli-runner.ts:67-84

Description

In runCliAgent, the new cron gate invokes the global plugin hook runner for before_agent_reply and passes potentially sensitive runtime context directly to hook handlers.

If plugin hooks can be authored/installed by third parties (or run in otherwise less-trusted environments), this is a data exposure risk:

  • sessionKey is provided to the hook context. Session keys are commonly used as bearer identifiers for accessing session state and may enable session takeover or access to other resources if exfiltrated.
  • workspaceDir is provided to the hook context, leaking local filesystem paths and potentially aiding targeted file access/exfiltration by hook code.
  • The hook runner executes hook code in-process (no sandboxing evident here), so a hook can trivially exfiltrate these values over the network.

Vulnerable code:

const hookContext = {
  runId: params.runId,
  agentId: params.agentId,
  sessionKey: params.sessionKey,
  sessionId: params.sessionId,
  workspaceDir: params.workspaceDir,
  ...
} as const;
const hookResult = await hookRunner.runBeforeAgentReply(
  { cleanedBody: params.prompt },
  hookContext,
);

Recommendation

Minimize the data exposed to before_agent_reply hooks (principle of least privilege):

  • Remove sessionKey and workspaceDir from the before_agent_reply context unless a hook demonstrably requires them.
  • If hooks need to act on session state, pass a scoped capability (or a narrow API) instead of the raw key/path.
  • If workspaceDir is needed, pass a redacted/virtual identifier (e.g., workspace ID) rather than an absolute path.

Example (omit sensitive fields):

const hookContext = {
  runId: params.runId,
  agentId: params.agentId,
  sessionId: params.sessionId,
  messageProvider: params.messageProvider,
  trigger: params.trigger,
  channelId: params.messageChannel ?? params.messageProvider,
} as const;

If retaining these fields is necessary, document that hooks are fully trusted and consider adding a configuration flag that disables passing sensitive context to hooks by default.


Analyzed PR: #70950 at commit 2eeeb4b

Last updated on: 2026-04-24T05:59:11Z

@Patrick-Erichsen
Patrick-Erichsen merged commit d29eaea into openclaw:main Apr 24, 2026
63 of 65 checks passed
Angfr95 pushed a commit to Angfr95/openclaw that referenced this pull request Apr 25, 2026
…nclaw#70950)

* test(cli-runner): RED — assert before_agent_reply fires on cron triggers

Mirrors src/agents/pi-embedded-runner/run.before-agent-reply-cron.test.ts
for the CLI runner. Asserts:

1. When trigger=cron and a before_agent_reply hook claims the turn
   (handled: true), runCliAgent must NOT invoke the codex subprocess and
   must return the hook's reply text in payloads[0].
2. When the hook claims without a reply body, the synthesized payload
   uses SILENT_REPLY_TOKEN.
3. Non-cron triggers do not invoke the hook (no behavior change for
   normal user/heartbeat traffic).
4. Without a registered hook, falls through to the CLI subprocess.

Currently fails (RED): tests 1 and 2 fail because runCliAgent never
fires before_agent_reply — the hook gate exists only in the embedded PI
runner (src/agents/pi-embedded-runner/run.ts:326). This is the
CLI-backed-agent dreaming gap reported in openclaw#70940 and identified in
PR openclaw#70737 review.

Next commit: implement the hook gate in runPreparedCliAgent (GREEN).

* fix(cli-runner): GREEN — fire before_agent_reply for cron-triggered turns

Mirrors the embedded PI runner gate from
src/agents/pi-embedded-runner/run.ts:326 so plugin-managed cron jobs
(notably memory-core dreaming) can short-circuit a CLI-backed agent
turn before the codex/claude/gemini subprocess is spawned.

Without this, configuring a default agent's model to a CLI backend
(codex-cli, claude-cli, gemini-cli, or any third-party
`registerCliBackend` provider) silently broke dreaming: the cron
sentinel was sent to the underlying LLM as a literal user prompt and
the dreaming hook never executed. See openclaw#70940 for the
empirical repro (codex-cli observed sending the dream-token to GPT-5.5
with no `memory-core: dreaming promotion complete` line).

Also extracts `buildHandledReplyPayloads` locally; eventually that
should be unified with the embedded PI runner's helper, but that's a
mechanical refactor for a follow-up.

Closes openclaw#70940 once both this PR and openclaw#70737 land — this fix is only
useful if cron-driven dreaming exists, which is what openclaw#70737 introduces.

TDD trail:
- prior commit: RED test asserting the hook gate (4 cases)
- this commit: implementation that turns those tests green (4/4 pass).

Verified: pnpm test src/agents/cli-runner.before-agent-reply-cron.test.ts
4/4 passed; pnpm test src/agents/cli-runner 21/21 passed; lint clean
on touched files; pre-existing tsgo failure in
src/plugin-sdk/provider-tools.ts is unrelated to these changes.
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…nclaw#70950)

* test(cli-runner): RED — assert before_agent_reply fires on cron triggers

Mirrors src/agents/pi-embedded-runner/run.before-agent-reply-cron.test.ts
for the CLI runner. Asserts:

1. When trigger=cron and a before_agent_reply hook claims the turn
   (handled: true), runCliAgent must NOT invoke the codex subprocess and
   must return the hook's reply text in payloads[0].
2. When the hook claims without a reply body, the synthesized payload
   uses SILENT_REPLY_TOKEN.
3. Non-cron triggers do not invoke the hook (no behavior change for
   normal user/heartbeat traffic).
4. Without a registered hook, falls through to the CLI subprocess.

Currently fails (RED): tests 1 and 2 fail because runCliAgent never
fires before_agent_reply — the hook gate exists only in the embedded PI
runner (src/agents/pi-embedded-runner/run.ts:326). This is the
CLI-backed-agent dreaming gap reported in openclaw#70940 and identified in
PR openclaw#70737 review.

Next commit: implement the hook gate in runPreparedCliAgent (GREEN).

* fix(cli-runner): GREEN — fire before_agent_reply for cron-triggered turns

Mirrors the embedded PI runner gate from
src/agents/pi-embedded-runner/run.ts:326 so plugin-managed cron jobs
(notably memory-core dreaming) can short-circuit a CLI-backed agent
turn before the codex/claude/gemini subprocess is spawned.

Without this, configuring a default agent's model to a CLI backend
(codex-cli, claude-cli, gemini-cli, or any third-party
`registerCliBackend` provider) silently broke dreaming: the cron
sentinel was sent to the underlying LLM as a literal user prompt and
the dreaming hook never executed. See openclaw#70940 for the
empirical repro (codex-cli observed sending the dream-token to GPT-5.5
with no `memory-core: dreaming promotion complete` line).

Also extracts `buildHandledReplyPayloads` locally; eventually that
should be unified with the embedded PI runner's helper, but that's a
mechanical refactor for a follow-up.

Closes openclaw#70940 once both this PR and openclaw#70737 land — this fix is only
useful if cron-driven dreaming exists, which is what openclaw#70737 introduces.

TDD trail:
- prior commit: RED test asserting the hook gate (4 cases)
- this commit: implementation that turns those tests green (4/4 pass).

Verified: pnpm test src/agents/cli-runner.before-agent-reply-cron.test.ts
4/4 passed; pnpm test src/agents/cli-runner 21/21 passed; lint clean
on touched files; pre-existing tsgo failure in
src/plugin-sdk/provider-tools.ts is unrelated to these changes.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…nclaw#70950)

* test(cli-runner): RED — assert before_agent_reply fires on cron triggers

Mirrors src/agents/pi-embedded-runner/run.before-agent-reply-cron.test.ts
for the CLI runner. Asserts:

1. When trigger=cron and a before_agent_reply hook claims the turn
   (handled: true), runCliAgent must NOT invoke the codex subprocess and
   must return the hook's reply text in payloads[0].
2. When the hook claims without a reply body, the synthesized payload
   uses SILENT_REPLY_TOKEN.
3. Non-cron triggers do not invoke the hook (no behavior change for
   normal user/heartbeat traffic).
4. Without a registered hook, falls through to the CLI subprocess.

Currently fails (RED): tests 1 and 2 fail because runCliAgent never
fires before_agent_reply — the hook gate exists only in the embedded PI
runner (src/agents/pi-embedded-runner/run.ts:326). This is the
CLI-backed-agent dreaming gap reported in openclaw#70940 and identified in
PR openclaw#70737 review.

Next commit: implement the hook gate in runPreparedCliAgent (GREEN).

* fix(cli-runner): GREEN — fire before_agent_reply for cron-triggered turns

Mirrors the embedded PI runner gate from
src/agents/pi-embedded-runner/run.ts:326 so plugin-managed cron jobs
(notably memory-core dreaming) can short-circuit a CLI-backed agent
turn before the codex/claude/gemini subprocess is spawned.

Without this, configuring a default agent's model to a CLI backend
(codex-cli, claude-cli, gemini-cli, or any third-party
`registerCliBackend` provider) silently broke dreaming: the cron
sentinel was sent to the underlying LLM as a literal user prompt and
the dreaming hook never executed. See openclaw#70940 for the
empirical repro (codex-cli observed sending the dream-token to GPT-5.5
with no `memory-core: dreaming promotion complete` line).

Also extracts `buildHandledReplyPayloads` locally; eventually that
should be unified with the embedded PI runner's helper, but that's a
mechanical refactor for a follow-up.

Closes openclaw#70940 once both this PR and openclaw#70737 land — this fix is only
useful if cron-driven dreaming exists, which is what openclaw#70737 introduces.

TDD trail:
- prior commit: RED test asserting the hook gate (4 cases)
- this commit: implementation that turns those tests green (4/4 pass).

Verified: pnpm test src/agents/cli-runner.before-agent-reply-cron.test.ts
4/4 passed; pnpm test src/agents/cli-runner 21/21 passed; lint clean
on touched files; pre-existing tsgo failure in
src/plugin-sdk/provider-tools.ts is unrelated to these changes.
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
…nclaw#70950)

* test(cli-runner): RED — assert before_agent_reply fires on cron triggers

Mirrors src/agents/pi-embedded-runner/run.before-agent-reply-cron.test.ts
for the CLI runner. Asserts:

1. When trigger=cron and a before_agent_reply hook claims the turn
   (handled: true), runCliAgent must NOT invoke the codex subprocess and
   must return the hook's reply text in payloads[0].
2. When the hook claims without a reply body, the synthesized payload
   uses SILENT_REPLY_TOKEN.
3. Non-cron triggers do not invoke the hook (no behavior change for
   normal user/heartbeat traffic).
4. Without a registered hook, falls through to the CLI subprocess.

Currently fails (RED): tests 1 and 2 fail because runCliAgent never
fires before_agent_reply — the hook gate exists only in the embedded PI
runner (src/agents/pi-embedded-runner/run.ts:326). This is the
CLI-backed-agent dreaming gap reported in openclaw#70940 and identified in
PR openclaw#70737 review.

Next commit: implement the hook gate in runPreparedCliAgent (GREEN).

* fix(cli-runner): GREEN — fire before_agent_reply for cron-triggered turns

Mirrors the embedded PI runner gate from
src/agents/pi-embedded-runner/run.ts:326 so plugin-managed cron jobs
(notably memory-core dreaming) can short-circuit a CLI-backed agent
turn before the codex/claude/gemini subprocess is spawned.

Without this, configuring a default agent's model to a CLI backend
(codex-cli, claude-cli, gemini-cli, or any third-party
`registerCliBackend` provider) silently broke dreaming: the cron
sentinel was sent to the underlying LLM as a literal user prompt and
the dreaming hook never executed. See openclaw#70940 for the
empirical repro (codex-cli observed sending the dream-token to GPT-5.5
with no `memory-core: dreaming promotion complete` line).

Also extracts `buildHandledReplyPayloads` locally; eventually that
should be unified with the embedded PI runner's helper, but that's a
mechanical refactor for a follow-up.

Closes openclaw#70940 once both this PR and openclaw#70737 land — this fix is only
useful if cron-driven dreaming exists, which is what openclaw#70737 introduces.

TDD trail:
- prior commit: RED test asserting the hook gate (4 cases)
- this commit: implementation that turns those tests green (4/4 pass).

Verified: pnpm test src/agents/cli-runner.before-agent-reply-cron.test.ts
4/4 passed; pnpm test src/agents/cli-runner 21/21 passed; lint clean
on touched files; pre-existing tsgo failure in
src/plugin-sdk/provider-tools.ts is unrelated to these changes.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…nclaw#70950)

* test(cli-runner): RED — assert before_agent_reply fires on cron triggers

Mirrors src/agents/pi-embedded-runner/run.before-agent-reply-cron.test.ts
for the CLI runner. Asserts:

1. When trigger=cron and a before_agent_reply hook claims the turn
   (handled: true), runCliAgent must NOT invoke the codex subprocess and
   must return the hook's reply text in payloads[0].
2. When the hook claims without a reply body, the synthesized payload
   uses SILENT_REPLY_TOKEN.
3. Non-cron triggers do not invoke the hook (no behavior change for
   normal user/heartbeat traffic).
4. Without a registered hook, falls through to the CLI subprocess.

Currently fails (RED): tests 1 and 2 fail because runCliAgent never
fires before_agent_reply — the hook gate exists only in the embedded PI
runner (src/agents/pi-embedded-runner/run.ts:326). This is the
CLI-backed-agent dreaming gap reported in openclaw#70940 and identified in
PR openclaw#70737 review.

Next commit: implement the hook gate in runPreparedCliAgent (GREEN).

* fix(cli-runner): GREEN — fire before_agent_reply for cron-triggered turns

Mirrors the embedded PI runner gate from
src/agents/pi-embedded-runner/run.ts:326 so plugin-managed cron jobs
(notably memory-core dreaming) can short-circuit a CLI-backed agent
turn before the codex/claude/gemini subprocess is spawned.

Without this, configuring a default agent's model to a CLI backend
(codex-cli, claude-cli, gemini-cli, or any third-party
`registerCliBackend` provider) silently broke dreaming: the cron
sentinel was sent to the underlying LLM as a literal user prompt and
the dreaming hook never executed. See openclaw#70940 for the
empirical repro (codex-cli observed sending the dream-token to GPT-5.5
with no `memory-core: dreaming promotion complete` line).

Also extracts `buildHandledReplyPayloads` locally; eventually that
should be unified with the embedded PI runner's helper, but that's a
mechanical refactor for a follow-up.

Closes openclaw#70940 once both this PR and openclaw#70737 land — this fix is only
useful if cron-driven dreaming exists, which is what openclaw#70737 introduces.

TDD trail:
- prior commit: RED test asserting the hook gate (4 cases)
- this commit: implementation that turns those tests green (4/4 pass).

Verified: pnpm test src/agents/cli-runner.before-agent-reply-cron.test.ts
4/4 passed; pnpm test src/agents/cli-runner 21/21 passed; lint clean
on touched files; pre-existing tsgo failure in
src/plugin-sdk/provider-tools.ts is unrelated to these changes.
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…nclaw#70950)

* test(cli-runner): RED — assert before_agent_reply fires on cron triggers

Mirrors src/agents/pi-embedded-runner/run.before-agent-reply-cron.test.ts
for the CLI runner. Asserts:

1. When trigger=cron and a before_agent_reply hook claims the turn
   (handled: true), runCliAgent must NOT invoke the codex subprocess and
   must return the hook's reply text in payloads[0].
2. When the hook claims without a reply body, the synthesized payload
   uses SILENT_REPLY_TOKEN.
3. Non-cron triggers do not invoke the hook (no behavior change for
   normal user/heartbeat traffic).
4. Without a registered hook, falls through to the CLI subprocess.

Currently fails (RED): tests 1 and 2 fail because runCliAgent never
fires before_agent_reply — the hook gate exists only in the embedded PI
runner (src/agents/pi-embedded-runner/run.ts:326). This is the
CLI-backed-agent dreaming gap reported in openclaw#70940 and identified in
PR openclaw#70737 review.

Next commit: implement the hook gate in runPreparedCliAgent (GREEN).

* fix(cli-runner): GREEN — fire before_agent_reply for cron-triggered turns

Mirrors the embedded PI runner gate from
src/agents/pi-embedded-runner/run.ts:326 so plugin-managed cron jobs
(notably memory-core dreaming) can short-circuit a CLI-backed agent
turn before the codex/claude/gemini subprocess is spawned.

Without this, configuring a default agent's model to a CLI backend
(codex-cli, claude-cli, gemini-cli, or any third-party
`registerCliBackend` provider) silently broke dreaming: the cron
sentinel was sent to the underlying LLM as a literal user prompt and
the dreaming hook never executed. See openclaw#70940 for the
empirical repro (codex-cli observed sending the dream-token to GPT-5.5
with no `memory-core: dreaming promotion complete` line).

Also extracts `buildHandledReplyPayloads` locally; eventually that
should be unified with the embedded PI runner's helper, but that's a
mechanical refactor for a follow-up.

Closes openclaw#70940 once both this PR and openclaw#70737 land — this fix is only
useful if cron-driven dreaming exists, which is what openclaw#70737 introduces.

TDD trail:
- prior commit: RED test asserting the hook gate (4 cases)
- this commit: implementation that turns those tests green (4/4 pass).

Verified: pnpm test src/agents/cli-runner.before-agent-reply-cron.test.ts
4/4 passed; pnpm test src/agents/cli-runner 21/21 passed; lint clean
on touched files; pre-existing tsgo failure in
src/plugin-sdk/provider-tools.ts is unrelated to these changes.
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…nclaw#70950)

* test(cli-runner): RED — assert before_agent_reply fires on cron triggers

Mirrors src/agents/pi-embedded-runner/run.before-agent-reply-cron.test.ts
for the CLI runner. Asserts:

1. When trigger=cron and a before_agent_reply hook claims the turn
   (handled: true), runCliAgent must NOT invoke the codex subprocess and
   must return the hook's reply text in payloads[0].
2. When the hook claims without a reply body, the synthesized payload
   uses SILENT_REPLY_TOKEN.
3. Non-cron triggers do not invoke the hook (no behavior change for
   normal user/heartbeat traffic).
4. Without a registered hook, falls through to the CLI subprocess.

Currently fails (RED): tests 1 and 2 fail because runCliAgent never
fires before_agent_reply — the hook gate exists only in the embedded PI
runner (src/agents/pi-embedded-runner/run.ts:326). This is the
CLI-backed-agent dreaming gap reported in openclaw#70940 and identified in
PR openclaw#70737 review.

Next commit: implement the hook gate in runPreparedCliAgent (GREEN).

* fix(cli-runner): GREEN — fire before_agent_reply for cron-triggered turns

Mirrors the embedded PI runner gate from
src/agents/pi-embedded-runner/run.ts:326 so plugin-managed cron jobs
(notably memory-core dreaming) can short-circuit a CLI-backed agent
turn before the codex/claude/gemini subprocess is spawned.

Without this, configuring a default agent's model to a CLI backend
(codex-cli, claude-cli, gemini-cli, or any third-party
`registerCliBackend` provider) silently broke dreaming: the cron
sentinel was sent to the underlying LLM as a literal user prompt and
the dreaming hook never executed. See openclaw#70940 for the
empirical repro (codex-cli observed sending the dream-token to GPT-5.5
with no `memory-core: dreaming promotion complete` line).

Also extracts `buildHandledReplyPayloads` locally; eventually that
should be unified with the embedded PI runner's helper, but that's a
mechanical refactor for a follow-up.

Closes openclaw#70940 once both this PR and openclaw#70737 land — this fix is only
useful if cron-driven dreaming exists, which is what openclaw#70737 introduces.

TDD trail:
- prior commit: RED test asserting the hook gate (4 cases)
- this commit: implementation that turns those tests green (4/4 pass).

Verified: pnpm test src/agents/cli-runner.before-agent-reply-cron.test.ts
4/4 passed; pnpm test src/agents/cli-runner 21/21 passed; lint clean
on touched files; pre-existing tsgo failure in
src/plugin-sdk/provider-tools.ts is unrelated to these changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

memory/dreaming: CLI-backed agents (Claude CLI, Codex CLI, Gemini CLI) silently fail to dream

1 participant