Skip to content

feat(hooks): postHookActions mechanism, blockSessionSave/sessionSaveContent, and slug-generation hardening#38161

Closed
zeroaltitude wants to merge 71 commits intoopenclaw:mainfrom
zeroaltitude:feat/hook-session-memory-block-content
Closed

feat(hooks): postHookActions mechanism, blockSessionSave/sessionSaveContent, and slug-generation hardening#38161
zeroaltitude wants to merge 71 commits intoopenclaw:mainfrom
zeroaltitude:feat/hook-session-memory-block-content

Conversation

@zeroaltitude
Copy link
Copy Markdown
Contributor

@zeroaltitude zeroaltitude commented Mar 6, 2026

Summary

Introduces a postHookActions deferred-execution mechanism on InternalHookEvent and uses it to add two new context fields — blockSessionSave and sessionSaveContent — to the bundled session-memory handler. These let upstream hooks suppress or replace what gets written to the memory file, regardless of registration order.

Separately hardens the LLM slug-generation path against prompt injection and filename collisions.

Scope note

This PR extends slightly beyond its original scope to include a universal filename uniqueness fix. During code review, analysis of the TOCTOU (time-of-check-to-time-of-use) concerns around the post-hook retraction and content replacement paths revealed that the root cause — potential filename collisions between concurrent sessions — was better addressed at the source than with increasingly complex atomic file operations. By appending a random suffix to all memory filenames (not just the fallback timestamp path), we eliminate the collision prerequisite entirely, which makes the downstream TOCTOU concerns moot and avoids the need for file locking infrastructure. This is a pre-existing data loss bug (silent overwrites) that the new post-hook mechanisms would have inherited, so fixing it here prevents compounding complexity.

Key Changes

postHookActions mechanism (src/hooks/internal-hooks.ts)

  • Adds a postHookActions array to InternalHookEvent
  • triggerInternalHook drains it sequentially after all handlers complete, with per-action error isolation
  • Post-hook drain runs even when no handlers are registered (no early-return footgun)
  • Architectural foundation that makes the following new controls possible

Session-memory persistence control (src/hooks/bundled/session-memory/handler.ts)

  • blockSessionSave — suppresses memory file creation entirely
  • sessionSaveContent — replaces the default transcript-based entry with custom content
  • blockSessionSave takes precedence over sessionSaveContent in both inline and deferred paths
  • Fail-safe inline-write pattern: file is always written inline (preserving data if the drain never runs); the post-hook callback retracts or overwrites once later handlers have set their flags
  • Pre-set short-circuit: when blockSessionSave is already set before the handler runs, transcript loading and LLM slug generation are skipped entirely — no unnecessary data processing or model calls

Universal filename uniqueness (src/hooks/bundled/session-memory/handler.ts)

  • All memory filenames now include a 4-char random hex suffix (e.g. 2026-03-21-debugging-slack-cache-a7f3.md), applied to both LLM-slug and fallback timestamp paths
  • Fixes a pre-existing data loss bug: LLM-generated slugs are descriptive but not unique — two similar sessions on the same day could produce identical slugs, causing the second write to silently overwrite the first
  • Eliminates the filename collision prerequisite for TOCTOU races in the post-hook retraction/replacement paths

Slug-generation hardening (src/hooks/llm-slug-generator.ts)

  • disableTools: true on the embedded agent call — prevents prompt injection via attacker-controllable conversation content
  • Collision-resistant fallback slugHHMMSS timestamp (previously HHMM) for the no-LLM path

Minor

  • .gitignore: adds .openclaw-*/ to suppress test workspace directories

Test Coverage

  • 8 new integration tests in handler.test.ts: pre-set/late-set block, pre-set/late-set custom content, block+content precedence, blank-marker empty string, fail-safe inline write, and mkdir regression
  • 6 new unit tests in internal-hooks.test.ts: ordering, context visibility, per-action error isolation, no-handlers drain, and early-return removal
  • All 28 session-memory tests pass

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 6, 2026

Greptile Summary

This PR introduces the postHookActions deferred-execution mechanism on InternalHookEvent, adds blockSessionSave/sessionSaveContent context fields to the session-memory handler, hardens slug generation against prompt injection by setting disableTools: true, and upgrades all memory filenames to include an 8-char random hex suffix to eliminate silent-overwrite collisions. The design is well-considered and the new integration tests cover the key invariants (pre-set/late-set block, content replacement, precedence, fail-safe, mkdir edge case, CWE-834 guard).

Confidence Score: 5/5

Safe to merge; only two minor P2 findings.

No P0 or P1 issues found. The core postHookActions mechanism, fail-safe write pattern, TOCTOU ownership checks, and CWE-834 snapshot guard are all correctly implemented and well-tested. The two findings are P2 only: a stale '4-char' comment in the test and a PR-description claim about HHMMSS that the code doesn't actually implement (collision avoidance is fully addressed by the 8-char suffix regardless).

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/hooks/bundled/session-memory/handler.test.ts
Line: 73-77

Comment:
**Stale comment says "4-char" but suffix is 8 chars**

The comment on line 73 reads "4-char hex random suffix" while the assertion on line 77 tests for exactly 8 hex characters (`[0-9a-f]{8}`), which matches the implementation (`slice(0, 8)`). The comment was not updated when the suffix was widened from 4 to 8 chars.

```suggestion
      // Filename includes an 8-char hex random suffix on all paths
      // (LLM and fallback) to guarantee uniqueness; the timestamp portion
      // is what we assert here.
```

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

---

This is a comment left during a code review.
Path: src/hooks/bundled/session-memory/handler.ts
Line: 83-86

Comment:
**PR description claims HHMMSS fallback slug but code still produces HHMM**

The PR description states: *"Collision-resistant fallback slug — `HHMMSS` timestamp (previously `HHMM`) for the no-LLM path."* However, `timeSlug` is still built as `${hour}${minute}` (4 digits, HHMM), and the test regex `2330` confirms this. The `second` variable is computed (line 75) but not included in the slug. Since the 8-char unique suffix provides all necessary collision avoidance, this doesn't affect correctness — but the PR description's claim is inaccurate and `second` goes unused.

```suggestion
    timeSlug: `${hour}${minute}${second}`,
```

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

Reviews (39): Last reviewed commit: "fix(session-memory): address gpt-5.5 dee..." | Re-trigger Greptile

Comment thread src/hooks/bundled/session-memory/handler.ts
Comment thread src/hooks/bundled/session-memory/handler.test.ts Outdated
@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@greptile-apps: Please re-score after minor fixes!

@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@greptile-apps: resolved minor issue, please re-score

Copy link
Copy Markdown

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

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

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/hooks/bundled/session-memory/handler.ts Outdated
@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@greptile-apps: Please re-score, fixes added

Copy link
Copy Markdown

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

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: d022dd707b

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/hooks/bundled/session-memory/handler.ts Outdated
@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@greptile-apps Please rescore.

Copy link
Copy Markdown

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

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: 9670dd0923

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/hooks/bundled/session-memory/handler.ts Outdated
@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@greptile-apps Please rescore.

1 similar comment
@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@greptile-apps Please rescore.

Copy link
Copy Markdown

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

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: 7295c0d2e7

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/hooks/bundled/session-memory/handler.ts Outdated
@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@greptile-apps: We have re-thought this feature and believe that we need a slightly more powerful hooks feature: postHookActions. A generic deferred-execution mechanism on InternalHookEvent. Any handler can push async callbacks onto event.postHookActions. After all handlers in the FIFO loop have completed, triggerInternalHook drains the queue sequentially.

This is not session-memory-specific. Any hook that needs "act after everyone has spoken" semantics can use it.

Design constraint: fail-safe: The critical invariant: if anything goes wrong in the hooks system, session memory must still be written. Data preservation beats policy enforcement.

This means the session-memory handler cannot defer its write to postHookActions — if the drain never runs (bug, exception, crash), the file would never exist. Instead:

  1. Write inline (as today) — the file hits disk during the handler's normal execution
  2. Push a post-hook action for retraction (blockSessionSave) or replacement (sessionSaveContent)
  3. If postHookActions never drains → file stays on disk with best available content
  4. If a later hook sets blockSessionSave → post-hook action deletes the file
  5. If a later hook sets sessionSaveContent → post-hook action overwrites with new content

Pre-set flags (set before the handler runs by an upstream hook) still work inline — the handler checks blockSessionSave before writing and uses sessionSaveContent directly. The post-hook action is a second pass that catches late-set flags.

Please consider this design change and re-score!

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 7, 2026

Excellent design! The postHookActions mechanism is elegant and the fail-safe approach is exactly right. However, I found a bug in the edge case handling.

The Issue:

When both blockSessionSave and sessionSaveContent are set (especially if blockSessionSave is pre-set), the post-hook action creates a file that shouldn't exist:

// In the post-hook action:
if (event.context.blockSessionSave === true && writtenEntry !== null) {
  // delete and return
}

// This runs even if blockSessionSave is true (but writtenEntry was null)
if (typeof postContent === "string" && postContent !== writtenEntry) {
  await writeFileWithinRoot(...); // ❌ Creates file despite blockSessionSave!
}

Trace:

  • blockSessionSave pre-set → inline write skipped → writtenEntry = null
  • Post-hook: delete check fails (writtenEntry is null)
  • Post-hook: overwrite check succeeds → creates new file (violates blockSessionSave intent)

Fix:

The sessionSaveContent logic should respect blockSessionSave:

const postContent = event.context.sessionSaveContent;
if (
  event.context.blockSessionSave !== true &&  // ✅ Add this check
  typeof postContent === "string" &&
  postContent !== writtenEntry
) {
  await writeFileWithinRoot({
    rootDir: memoryDir,
    relativePath: filename,
    data: postContent,
    encoding: "utf-8",
  });
  log.debug("Session save content replaced by post-hook (sessionSaveContent)", {
    length: postContent.length,
  });
}

Missing Test Coverage:

None of the tests set both flags. Add:

it("blockSessionSave takes precedence over sessionSaveContent", async () => {
  // Test both pre-set and one late-set scenarios
});

Otherwise: Implementation is solid, tests are comprehensive, and the fail-safe guarantees are preserved. This is a strong architecture improvement.

@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@darfaz Tagging you in the split. We've improved the architecture considerably while positing a powerful new hooks mechanism: postHookActions.

@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@greptile-apps: fixed blockSessionSave semantics and added test; please re-score!

Copy link
Copy Markdown

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

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: ae220e17bc

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/hooks/bundled/session-memory/handler.ts Outdated
@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@greptile-apps Please rescore.

Resolved 2 conflicts in src/hooks/bundled/session-memory/handler.ts:
- Adopted mainline's localTimestamp.timeSlug helper (replaces our
  inline timestamp computation)
- Kept our blockPreSet/hasCustomContent dispatch but adopted mainline's
  improved heading format with timeZoneSuffix
…stHookActions

drainPostHookActions has used the snapshot+clear pattern since the
postHookActions feature was extracted into a shared helper, so the
self-append DoS surface flagged by Aisle on PR openclaw#38162 (CWE-834,
unbounded for..of over a live array) is already not present on this
branch. This commit adds explicit regression coverage so the boundary
is exercised by tests rather than left implicit:

- 'prevents self-appending action loops by snapshotting before
  draining (CWE-834 guard)' — pushes a self-appending action and
  verifies the drain runs exactly once and the re-pushed action is
  queued for a subsequent triggerInternalHook call (not extended into
  the current cycle). Has a SAFETY_CAP of 1000 iterations so a broken
  drainer fails the assertion rather than hanging the test runner.

- 'snapshots actions registered after the drain begins (multi-handler
  stacking)' — verifies actions added by an action during drain are
  queued for a future cycle, not extended into the current one.

Also: the 'uses local timezone date and fallback time' test asserted
the pre-uniqueness-suffix filename format. Updated to match the
current format that includes a 4-char hex random suffix on all paths
(LLM and fallback) — the production behaviour was already correct,
the test was lagging.
Addresses Codex P2 review on PR openclaw#38161 (id=3037871646): the 4-char
(16-bit, ~65k possibilities) random suffix was inadequate for the
rapid-/new + multi-channel workloads this collision-prevention is
meant to protect against. By the birthday paradox, collision
probability for 256 same-day same-slug saves was already ~50% —
'collisions remain plausible in real workloads where many saves
share the same date+slug,' as the reviewer noted.

Switch to 8 hex chars (32 bits = ~4.3B possibilities). Realistic
same-day same-slug saves (a few dozen at most) now have
birthday-paradox collision probability < 1 in 1e8. The change is
contained to one line in the suffix derivation; comment updated to
explain the entropy budget calculation.

Test updated: the 'uses local timezone date and fallback time'
assertion regex changed from {4} to {8} hex chars to match the new
filename format.

Note: Codex P2 (id=3083160656) flagging hardcoded 15s slug-generation
timeout is already addressed in the current code — line 61 of
llm-slug-generator.ts uses resolveSlugGeneratorTimeoutMs(params.cfg)
which respects agents.defaults.timeoutSeconds. The 15_000 literal at
line 22 is now just the DEFAULT_SLUG_GENERATOR_TIMEOUT_MS constant
used as the fallback value when no config is set.
zeroaltitude added a commit to zeroaltitude/openclaw that referenced this pull request Apr 27, 2026
Two Codex review fixes for PR openclaw#38162:

1. Codex P2 (id=3128555543): Guard content blocks before reading .type
   in the inline transcript parser. The inlined private getRecentSessionContent
   in handler.ts reads c.type directly on every array item, so a
   null/undefined or non-object entry before a valid text block throws
   a TypeError, which the per-line catch swallows, dropping the entire
   message from the memory transcript. The shared transcript.ts helper
   handled this defensively; the inlined copy did not. Add typeof-object
   guard to the .find() predicate.

2. Codex P2 (id=3037871646 on PR openclaw#38161): Increase filename suffix
   entropy from 16 to 32 bits. The 4-char (~65k possibility) suffix
   gave ~50% birthday-paradox collision probability for 256 same-day
   same-slug saves — inadequate for the rapid-/new + multi-channel
   workloads this is meant to protect against. 8 hex chars (~4.3B
   possibilities) drops collision probability for realistic same-day
   same-slug saves to < 1 in 1e8.

New tests:
- 'survives malformed content blocks (null/non-object entries)
  without dropping the message': pushes content arrays containing
  null and a raw string before the text block; verifies both user
  and assistant messages still make it through to the memory
  transcript.

Existing 'uses local timezone date and fallback time' test regex
updated from {4} to {8} hex chars to match the new filename format.

Tests: 78/78 pass.
…okActions docs

Address remaining clawsweeper concerns on PR openclaw#38161 about public hook-
runtime surface, premature save logging, and late-blockSessionSave
privacy semantics:

1. **Public-surface docs on InternalHookEvent.postHookActions**
   The previous one-line JSDoc was inadequate for a public hook-runtime
   surface change. Replaced with comprehensive docs covering: when to
   use the field, snapshot+clear drain semantics (CWE-834 protection),
   per-action error isolation, that the field is optional/additive
   (existing handlers continue to work unchanged), and the explicit
   contract that actions pushed during drain are queued for the next
   cycle, NOT extended into the current one. Plugin authors and
   maintainers reviewing API compatibility now have the full contract
   in one place.

2. **Disambiguate inline-save log lines from final disposition**
   The previous 'Memory file written successfully' / 'Session context
   saved to' log lines would emit before the post-hook drain decided
   whether to retract or replace the file, leaving audit trails that
   misleadingly suggested completion when the final file might be
   different (or absent). Renamed to make the staging vs final state
   explicit:
     - debug: 'Memory file written inline (pre-post-hook, may be
       retracted/replaced)'
     - info:  'Session context staged at <path> (final disposition
       decided by post-hook drain)'
   Operators grepping for save success should filter on the post-drain
   'Session save retracted' / 'replaced' lines instead.

3. **Louder privacy warning on late blockSessionSave**
   The previous warning communicated 'data may have been sent' but did
   not provide the byte count or correlate to operator audit logs.
   Tightened to:
     - Use log level WARN with a 'PRIVACY:' prefix for grep-ability
     - Include transcript byte count so operators can size the egress
     - State explicitly that retraction does NOT un-send the data from
       the slug-generation provider's logs / training pipeline
     - Re-emphasize the mitigation (set blockSessionSave BEFORE the
       handler runs)
   The warning now matches the privacy posture clawsweeper asked for:
   loud, specific, actionable.

Tests: 70/70 still pass. The existing 'blockSessionSave (late-set)
retracts memory file via postHookActions' test exercises this code path.
@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@clawsweeper — addressed the open maintainer-level questions you flagged on this PR. Recent commits:

bd14845 — public-surface docs, log clarity, privacy posture

  1. Public hook-runtime surface (InternalHookEvent.postHookActions). Previous JSDoc was a single line, inadequate for the API-compatibility / plugin-migration concern you raised. Replaced with comprehensive docs covering: when to use the field, snapshot+clear drain semantics with explicit CWE-834 callout, per-action error isolation, optional/additive nature (existing handlers unchanged), and the explicit contract that actions pushed during drain are queued for the next cycle, NOT extended into the current one. Plugin authors and maintainers now have the full contract in one place.

  2. Premature save logging. The pre-existing Memory file written successfully / Session context saved to log lines emitted before the post-hook drain decided whether to retract or replace the file — misleading audit trail. Renamed to make staging vs final disposition explicit: Memory file written inline (pre-post-hook, may be retracted/replaced) and Session context staged at <path> (final disposition decided by post-hook drain). Operators grepping for save completion should filter on post-drain lines (Session save retracted / replaced).

  3. Late-blockSessionSave privacy semantics. Tightened the warning to match the loud-and-specific posture you asked for:

    • Uses WARN level with a PRIVACY: prefix for grep-ability.
    • Includes the transcript byte count so operators can size the egress.
    • Explicitly states that retraction does NOT un-send the data from the slug-generation provider's logs / training pipeline.
    • Re-emphasizes the mitigation (set blockSessionSave BEFORE the handler runs).

e812c0f — filename suffix entropy (already pushed earlier today)

  1. 4-hex character suffix entropy. Bumped from 4 hex chars (16 bits, ~50% birthday-paradox collision at 256 same-day same-slug saves) to 8 hex chars (32 bits, < 1 in 1e8 collision probability for realistic loads). Test regex updated to match.

6f63e10 — CWE-834 self-append guard tests (also already pushed)

Explicit regression tests for the snapshot+clear drain pattern, including a self-appending action test (with safety cap to prevent test runner hang) and a multi-handler stacking test.


On your split-the-PR suggestion — I'd defer to maintainer preference. The three logical pieces (slug-generation hardening with disableTools, postHookActions mechanism, session-memory block/content controls with stronger filename uniqueness) are coherent enough to land together as one focused PR, and the test surface across them is interleaved (the post-hook callback's behavior depends on the inline write semantics, which depend on the slug-generation outcome). If you want them split, happy to do that — just say the word.

On testing readiness — local vitest run src/hooks/bundled/session-memory/ src/hooks/internal-hooks.test.ts shows 70/70 passing. Full suite at the relevant scopes is green.

Ready for fresh review.

zeroaltitude added a commit to zeroaltitude/openclaw that referenced this pull request Apr 27, 2026
…e docs

Address remaining clawsweeper concerns on PR openclaw#38162:

1. **Public hook-runtime surface (postHookActions on InternalHookEvent).**
   The PR adds postHookActions to InternalHookEvent which is re-exported
   through src/plugin-sdk/hook-runtime.ts. clawsweeper flagged that
   bounded drain behavior + compatibility for manually-constructed
   events need to be settled. The drainer already does ??= [] before
   iteration (so manually-constructed legacy events without the field
   work fine), but lacked an explicit regression test. Added:

   - 'tolerates manually-constructed events that omit postHookActions
     (SDK compat)' — builds a hand-rolled event without the
     postHookActions field and triggers it. Verifies the drain
     doesn't throw, the registered handler runs, and no crash.

   Plus replaced the previous one-line JSDoc on InternalHookEvent's
   postHookActions field with comprehensive docs covering when to use
   it, snapshot+clear drain semantics with explicit CWE-834 callout,
   per-action error isolation, optional/additive nature, and the
   contract that actions pushed during drain are queued for the next
   cycle (not extended into the current one). Plugin authors and
   maintainers reviewing API compatibility now have the full contract
   in one place.

2. **Late-blockSessionSave privacy semantics.** Mirror the privacy
   warning improvement from openclaw#38161 onto this branch: when a late hook
   sets blockSessionSave AND the transcript was already loaded for
   slug generation, emit a WARN-level log with PRIVACY: prefix that
   includes the transcript byte count, the file path being retracted,
   and explicit acknowledgement that retraction does NOT un-send the
   data from the slug-generation provider's logs / training pipeline.

3. **Premature save logging.** Renamed inline log lines to make
   staging vs final disposition explicit:
     - debug: 'Memory file written inline (pre-post-hook, may be
       retracted/replaced)'
     - info:  'Session context staged at <path> (final disposition
       decided by post-hook drain)'
   Both redirect and non-redirect paths get the same treatment so
   operators grepping for save success filter on post-drain lines.

Tests: 76/76 pass (was 75; +1 for SDK compat).
gpt-5.5 deep review on PR openclaw#38161 identified three P2 items. None are
blocking, but all three are quick to land and improve operability /
documentation:

P2-1 — Privacy boundary on late-set sessionSaveContent
  When sessionSaveContent is set by a hook AFTER session-memory has
  already run, the LLM-generated slug from the original transcript is
  baked into the filename. The post-hook callback rewrites the file
  CONTENT but cannot change the filename, so the conversation topic
  may still be inferable from the on-disk filename. This is a design
  limitation, not a bug — but plugin authors need to know about it.
  Added a PRIVACY NOTE in the handler explaining when to use
  blockSessionSave vs. pre-set sessionSaveContent vs. late-set.

P2-2 — Final-disposition log level mismatch
  log.info "Session context staged at …" fires before post-hook
  actions. After the drain, the actual outcome (retraction, content
  replacement) was logged at log.debug, invisible to operators
  filtering at INFO level. Promoted to log.info on the four
  disposition messages:
    * "Session save retracted by post-hook — pre-existing file restored
       after external deletion"
    * "Session save retracted by post-hook — pre-existing file restored"
    * "Session save retracted by post-hook (blockSessionSave)"
    * "Session save content replaced by post-hook (sessionSaveContent)"
  The "Session save retraction skipped — file already removed" message
  stays at debug because it's informational, not a final disposition.

P2-3 — Documentation not updated for new features
  docs/automation/hooks.md still described the old YYYY-MM-DD-slug.md
  filename and made no mention of the new session-memory controls.
  Updated the session-memory details section to document:
    * The 8-hex-char random suffix on filenames (eliminates the prior
      silent-overwrite data loss path)
    * blockSessionSave and sessionSaveContent context fields with
      precedence rules (block always wins)
    * Pre-set vs. late-set semantics and the recommended path for
      privacy-critical suppression
    * The postHookActions mechanism for plugin authors writing
      ordering-independent hooks (with snapshot-before-drain CWE-834
      guard, per-action try/catch, onError routing)
    * A worked example (managed hook blocks save for secret: channels)

Tests: 77/77 still pass across session-memory, internal-hooks, and
boot-md test files.

Per gpt-5.5: no P0/P1 issues remain after the merge. Architectural
recommendation is land as-is once these P2 polish items are in.
@openclaw-barnacle openclaw-barnacle Bot added the docs Improvements or additions to documentation label Apr 27, 2026
@zeroaltitude
Copy link
Copy Markdown
Contributor Author

@greptile-apps: please re-review, and thank you

@openclaw-barnacle openclaw-barnacle Bot added channel: line Channel integration: line commands Command implementations agents Agent runtime and tooling labels May 1, 2026
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. and removed commands Command implementations labels May 5, 2026
Copy link
Copy Markdown

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

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: a1fa3068b3

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// and per-action isolation.
// Defensive: normalize for direct callers that bypass triggerInternalHook.
event.postHookActions ??= [];
event.postHookActions.push(async () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Run session-memory reconciliation after all post-hook mutations

session-memory enqueues its reconciliation callback during handler execution, but triggerInternalHook drains postHookActions in push order, so this callback will run before post-hook callbacks pushed by later-registered handlers. In flows where a downstream hook sets blockSessionSave or sessionSaveContent inside its own postHookActions callback, the session-memory callback executes too early and misses the final policy, leaving a file that should have been blocked or replaced.

Useful? React with 👍 / 👎.

// provider for slug generation — but only when the transcript was actually
// loaded (i.e. no custom content was pre-set). When hasCustomContent is
// true, transcript loading and LLM calls were skipped entirely.
if (!hasCustomContent && sessionContent) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Only emit LLM-egress warning when slug generation actually ran

This privacy warning is gated only on sessionContent presence, but slug generation is conditionally skipped when cfg is missing or LLM slugging is disabled/test-gated. In those cases no transcript is sent to a provider, yet the log still claims it was “already sent,” which creates false security/audit alerts and can trigger unnecessary incident response.

Useful? React with 👍 / 👎.

…ory-block-content

# Conflicts:
#	docs/automation/hooks.md
#	src/hooks/bundled/session-memory/handler.ts
Copy link
Copy Markdown

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

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: 93b9d5f74e

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// and per-action isolation.
// Defensive: normalize for direct callers that bypass triggerInternalHook.
event.postHookActions ??= [];
event.postHookActions.push(async () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Queue session-memory post-hook before trigger drain runs

This callback is enqueued inside saveSessionMemoryNow, but the exported handler starts that async function and returns immediately (saveSessionMemoryNow(event) is not awaited). In the normal triggerInternalHook flow, post-hook actions are drained right after handlers return, so the drain can run before this push happens (the callback is only reached after async transcript/file I/O). When that happens, late policy mutations from downstream hooks (blockSessionSave / sessionSaveContent) are never reconciled, so a save that should be retracted or replaced remains on disk.

Useful? React with 👍 / 👎.

@zeroaltitude zeroaltitude deleted the feat/hook-session-memory-block-content branch May 5, 2026 19:01
@zeroaltitude
Copy link
Copy Markdown
Contributor Author

Closing without merge — superseded by the upstream session-memory rework on main, which addresses the same concerns through a different (opt-in) design:

  • This PR's always-on slug + UUID-suffix uniqueness vs. main's opt-in hooks.internal.entries.session-memory.llmSlug: true + resolveAvailableMemoryFilename helper for collision avoidance.
  • The postHookActions / blockSessionSave / sessionSaveContent surface is not preserved here. If those control points are still desired, they can be re-proposed against the new handler shape in a fresh PR.

No downstream consumers in our ecosystem depend on this PR's specific API surface, so closing without follow-up. The branch is preserved as the annotated tag archive/feat/hook-session-memory-block-content on https://github.com/zeroaltitude/openclaw if recovery is ever needed.

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

Labels

agents Agent runtime and tooling channel: line Channel integration: line docs Improvements or additions to documentation size: XL 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.

3 participants