Skip to content

fix(cron): keep current/session-bound jobs out of direct channel sessions#57831

Closed
lttcnly wants to merge 5 commits into
openclaw:mainfrom
lttcnly:codex/cron-direct-session-guard
Closed

fix(cron): keep current/session-bound jobs out of direct channel sessions#57831
lttcnly wants to merge 5 commits into
openclaw:mainfrom
lttcnly:codex/cron-direct-session-guard

Conversation

@lttcnly

@lttcnly lttcnly commented Mar 30, 2026

Copy link
Copy Markdown

Summary

  • Problem: cron jobs could persist or reuse human direct-message session keys when sessionTarget="current" or session:<id> pointed at a real DM session.
  • Why it matters: cron runs could write back into live Feishu/Lark direct chats, pollute those sessions, and contribute to silent/misdirected replies.
  • What changed: add a small cron session-target guard during normalization, pass session context through tool/gateway update paths, and fall back to cron:<jobId> at runtime for legacy dirty direct targets.
  • What did NOT change (scope boundary): group/channel/topic/thread sessions still reuse safely, non-channel named sessions still work, and cron schema/types/docs were not broadened.

Maintainer context: CODEOWNERS should route this through @openclaw/secops; @tyler6204 may also want a look for cron behavior.

Change Type (select all)

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

Scope (select all touched areas)

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

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause / Regression History (if applicable)

  • Root cause: cron normalization treated sessionTarget="current" as “persist the current session key” without checking whether that session key belonged to a human direct-message chat, and runtime execution later trusted any persisted session:<id> target unconditionally.
  • Missing detection / guardrail: there was no eligibility guard for reusable direct-channel sessions at create/update time, and no runtime safety fallback for already-saved dirty jobs.
  • Prior context (git blame, prior PR, issue, or refactor if known): current/session-bound cron persistence already existed in src/cron/normalize.ts; this change adds the missing direct-session boundary without changing the public schema.
  • Why this regressed now: once cron started supporting current and persistent session:<id> bindings for agent turns, direct chat sessions became eligible by accident.
  • If unknown, what was ruled out: ruled out schema/type mismatches and session-store lookup requirements; the bug reproduces from session-key normalization/execution alone.

Regression Test Plan (if applicable)

  • 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/cron/normalize.test.ts, src/agents/tools/cron-tool.test.ts, src/gateway/server.cron.test.ts
  • Scenario the test should lock in: direct-chat current bindings degrade to isolated on add/update, explicit dirty direct session:<id> jobs fall back to cron:<jobId> at runtime, and safe group/named session reuse stays intact.
  • Why this is the smallest reliable guardrail: the bug spans normalization, tool/gateway seams, and runtime execution; these three layers cover the full path without adding heavy end-to-end setup.
  • Existing test that already covers this (if any): existing cron normalization/runtime coverage existed, but none asserted the direct-session boundary.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

  • sessionTarget="current" now only persists safe reusable sessions; human direct-message sessions fall back to isolated.
  • Explicit session:agent:...:direct:... targets are downgraded on create/update and ignored at runtime for legacy dirty jobs.
  • Safe group/channel/topic/thread reuse and non-channel named sessions continue to work as before.

Diagram (if applicable)

Before:
[current or session:direct] -> [persist real DM session key] -> [cron run writes into human DM]

After:
[current direct] -> [normalize to isolated]
[legacy session:direct] -> [runtime fallback to cron:<jobId>] -> [isolated cron run]
[safe group/named session] -> [reuse preserved]

Security Impact (required)

  • New permissions/capabilities? (Yes/No): No
  • Secrets/tokens handling changed? (Yes/No): No
  • New/changed network calls? (Yes/No): No
  • Command/tool execution surface changed? (Yes/No): No
  • Data access scope changed? (Yes/No): Yes
  • If any Yes, explain risk + mitigation: this reduces cron's data-access scope by preventing reuse of human direct-message sessions. Risk is limited to stricter isolation; mitigation is a runtime warn-and-fallback path plus tests preserving safe group/named-session reuse.

Repro + Verification

Environment

  • OS: macOS (local workstation)
  • Runtime/container: Node.js v24.14.0, pnpm 10.32.1
  • Model/provider: N/A
  • Integration/channel (if any): Feishu/Lark-style direct session keys and Discord-style group session keys
  • Relevant config (redacted): default local test config / mocked gateway test harness

Steps

  1. Create an agent-turn cron job from a direct chat with sessionTarget="current".
  2. Update the job (tool path and direct gateway RPC path) with sessionTarget="current" again.
  3. Force-run a legacy dirty job persisted with sessionTarget="session:agent:...:direct:...".

Expected

  • Add/update persist isolated instead of the human DM session.
  • Legacy dirty jobs run in cron:<jobId> instead of the human DM session.
  • Safe group/named session reuse is unchanged.

Actual

  • Verified locally with new unit/integration coverage.

Evidence

Attach at least one:

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

Human Verification (required)

  • Verified scenarios: targeted vitest coverage for normalize/tool/gateway runtime passed; pnpm build passed; pnpm check passed.
  • Edge cases checked: explicit direct session:<id> downgrade, current downgrade from direct chats, safe group reuse, named-session preservation when the name merely contains direct.
  • What you did not verify: full pnpm test is not green locally on this machine because unrelated existing failures remain in test/extension-test-boundary.test.ts and src/media-understanding/media-understanding-misc.test.ts, and one large unit batch hit a worker/OOM infrastructure failure.

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/No): Yes
  • Config/env changes? (Yes/No): No
  • Migration needed? (Yes/No): No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: a named session that mimics an internal agent-scoped direct-session key could now be treated as unsafe.
    • Mitigation: the guard only treats canonical agent-scoped direct chat keys as unsafe, and tests preserve ordinary named sessions.
  • Risk: legacy dirty jobs silently switching sessions could surprise operators.
    • Mitigation: runtime now emits a warning and falls back to the dedicated cron:<jobId> session instead of a human DM.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime agents Agent runtime and tooling size: M labels Mar 30, 2026
@greptile-apps

greptile-apps Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a security isolation gap where cron jobs could accidentally bind to human direct-message sessions when sessionTarget="current" or an explicit session:<direct-key> was used. The fix adds a three-layer guard: normalization time (create/update), tool/gateway RPC seam, and runtime execution fallback for legacy dirty jobs.

Key changes:

  • New src/cron/session-target-guard.ts introduces isUnsafeCronSessionKey, isUnsafeCronSessionTarget, and normalizeCronSessionTargetForPersistence, correctly detecting agent-scoped direct-chat keys via existing parseAgentSessionKey + deriveSessionChatType utilities.
  • normalizeCronJobInput in normalize.ts now calls the guard unconditionally (not gated on applyDefaults), which is the correct placement — patches also need "current" resolved at persistence time, and the old duplicate if (next.sessionTarget === "current") block was dead code.
  • cron-tool.ts and server-methods/cron.ts both forward sessionContext through the update path, which was previously missing.
  • server-cron.ts adds a runtime fallback for legacy persisted jobs that still carry a direct session target, warning and falling back to cron:<jobId>.
  • Tests cover all three layers (normalize, tool, gateway integration) and confirm safe group/named-session reuse is preserved.

Confidence Score: 5/5

Safe to merge — the guard is correct, well-tested across three layers, backward-compatible, and removes a genuine session-isolation gap.

No P0 or P1 issues found. The core guard logic handles mixed-case prefixes correctly. The removed duplicate block was dead code. Moving the guard outside applyDefaults is intentional and correct for the patch path. All test layers pass per the author.

No files require special attention. The session-target-guard.ts core logic and server-cron.ts runtime fallback are the two places worth a close read, and both are correct.

Important Files Changed

Filename Overview
src/cron/session-target-guard.ts New file introducing the direct-session guard. Logic is correct — direct-chat keys are detected via parseAgentSessionKey + deriveSessionChatType, and mixed-case 'SESSION:' prefixes are handled correctly since parseAgentSessionKey lowercases internally and both casings are 8 chars.
src/cron/normalize.ts Replaces old applyDefaults-gated 'current' resolution blocks (one dead code) with a single unconditional call to normalizeCronSessionTargetForPersistence. Moving outside applyDefaults is intentional and correct for the patch path.
src/agents/tools/cron-tool.ts Adds sessionContext passing to normalizeCronJobPatch in the update action. Normalized patch sent to gateway is idempotent on second normalization pass.
src/gateway/server-methods/cron.ts cron.update now extracts sessionKey, passes to normalizeCronJobPatch for context-aware resolution, and explicitly strips sessionKey before schema validation.
src/gateway/server-cron.ts Runtime safety net added: legacy persisted direct-session jobs fall back to cron: with a warning. Default sessionKey set at top of block ensures safe fallback.
src/gateway/server.cron.test.ts New integration test covers add/update direct→isolated, legacy dirty job runtime fallback, and safe group session reuse preservation.
src/cron/normalize.test.ts Four new unit tests cover current+direct→isolated, named session with 'direct' in name preserved, explicit direct target→isolated, and patch current+direct→isolated.
src/agents/tools/cron-tool.test.ts Two new unit tests verify the tool layer sends isolated to the gateway for both add and update actions from a direct chat session.

Reviews (1): Last reviewed commit: "fix(cron): keep current/session-bound jo..." | Re-trigger Greptile

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

Copy link
Copy Markdown

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: 8bc677f75b

ℹ️ 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/cron/session-target-guard.ts
@lttcnly lttcnly force-pushed the codex/cron-direct-session-guard branch from 92b17c7 to 27dbd59 Compare April 3, 2026 08:05

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

Copy link
Copy Markdown

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: 27dbd59d70

ℹ️ 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/cron/session-target-guard.ts

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

Copy link
Copy Markdown

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: 9ebaf2ab91

ℹ️ 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/cron/session-target-guard.ts
@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Keep this PR open: current main still has the direct cron session-reuse problem at source level, but the submitted patch is not merge-ready because it is dirty against main, has a stale import, uses an unsafe session classifier, and still lacks contributor real behavior proof.

Canonical path: Close this stale PR. The latest review rated it F, the branch still lacks merge-ready proof, and there has been no human follow-up after the durable review.

So I’m closing this here because the remaining work is already tracked in the canonical issue.

Review details

Best possible solution:

Close this stale PR. The latest review rated it F, the branch still lacks merge-ready proof, and there has been no human follow-up after the durable review.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level: current main still resolves direct current cron targets to session:<direct-key> and runtime cron execution uses persisted session:<id> targets. I did not run a live channel reproduction in this read-only review.

Is this the best way to solve the issue?

No, not as written. The layered guard is a plausible direction, but the branch needs a clean rebase, a canonical classifier, custom-session compatibility protection, maintainer policy approval, and real behavior proof before merge.

Security review:

Security review needs attention: Needs attention because this PR changes a security-sensitive cron/session boundary with an incomplete and over-broad classifier.

  • [high] Direct-session classifier is not a safe boundary — src/cron/session-target-guard.ts:17
    The proposed predicate relies on a broad chat-type display classifier and an agent-key parse gate, so it can both miss real direct-message session keys and downgrade safe custom session IDs.
    Confidence: 0.86

AGENTS.md: found and applied where relevant.

What I checked:

  • stale F-rated PR: PR was opened 2026-03-30T16:40:49Z, is older than 30 days, and the latest review rated it F.
  • proof blocker: real behavior proof is missing and proof tier is F, so this branch is not merge-ready without contributor follow-up.
  • no human follow-up: live comments and timeline hydrated by apply contain no non-automation activity after the ClawSweeper review.

Likely related people:

  • steipete: Recent GitHub commit history shows repeated cron normalization, gateway runtime helper, wake target resolution, and cron validation work around the affected files. (role: recent area contributor; confidence: high; commits: 15fa1e546f2b, 875c9fd96dd8, 45b5f876ddf0; files: src/cron/normalize.ts, src/cron/session-target.ts, src/gateway/server-cron.ts)
  • vincentkoc: Recent cron normalization and payload-model commits in the affected cron paths were authored or committed by vincentkoc. (role: recent cron normalizer contributor; confidence: medium; commits: e0546edd9820, cfeaf6897fd8; files: src/cron/normalize.ts, src/agents/tools/cron-tool.ts)
  • cavit99: Authored the merged adjacent cron direct-delivery/session mirroring work that overlaps the same direct cron delivery and session-state surface. (role: adjacent merged PR author; confidence: medium; commits: 48b4e5b3614f; files: src/cron/isolated-agent/delivery-dispatch.ts, src/gateway/server.cron.test.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against e9671ed60327.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels May 19, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 19, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jun 10, 2026
@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling gateway Gateway runtime merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: M stale Marked as stale due to inactivity status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant