Skip to content

fix: land #64, #65, #67 on rewritten master + README refresh - #73

Merged
codejunkie99 merged 8 commits into
masterfrom
chore/salvage-stale-prs
Sep 26, 2026
Merged

codejunkie99 merged 8 commits into
masterfrom
chore/salvage-stale-prs

Conversation

@codejunkie99

@codejunkie99 codejunkie99 commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Why

master history was rewritten after #64, #65, #67 and #68 were opened, so each of them shows ~31k lines / 231 files and conflicts. Each PR's real change is only its last 1–2 commits. This PR cherry-picks those commits (-x, original author kept) onto current master.

What

Review follow-ups (second commit)

  • Hook fallback reflection no longer dumps short tool inputs (NotebookEdit leak). Non-Bash detail stores output and error size, not their text (Read and error leaks). Out-of-tree paths become <external>.
  • MiniMax OpenAI wire: max_tokens. max_completion_tokens raises TypeError on the supported minimum openai==1.40.0 (verified locally).
  • Loop allowlist: phase_started / running (desktop supervisor).
  • README/CHANGELOG: 14 seed skills, not 15.

Not included

Verification

  • pytest: 183 passed
  • tests/test_claude_code_hook.py: 69/69 (the 5 new privacy checks fail on the pre-fix code)
  • .agent/tools/test_learn_episodic_mirror.py: OK
  • verify_codex_fixes.py: all regression checks passed

Supersedes #64, #65, #67. Closes #66.

🤖 Generated with Claude Code

Note

Redact raw paths and tool content from Claude Code episodic entries and allowlist loop exports

  • Adds _normalize_path in claude_code_post_tool.py so episodic action labels, reflections, and details store project-relative or tilde paths instead of raw absolute paths, with outside paths labeled <external> and invalid values as ?.
  • Replaces raw edit strings, tool inputs, output, and error text in reflections and details with normalized paths and character counts.
  • Adds finite allowlists for loop event names, statuses, and decisions in data_layer_export.py; unrecognized values export as unknown.
  • Forwards max_tokens to the MiniMax OpenAI-wire request in llm.py, and adds regression tests for path/content redaction and loop-value allowlisting.
  • Risk: episodic entries now lose raw edit contents, tool output, and error text — consumers relying on _detail/_reflection fields in claude_code_post_tool.py must use the new counts and normalized paths; unrecognized loop event/status/decision text is dropped from exports.

Macroscope summarized e6f8b95.

RetriggerConfidence Score: 5/5

The PR appears safe to merge based on the reviewed changes.

Summary

The PR combines loop-export redaction, MiniMax token-limit forwarding, Claude Code hook privacy changes, test repairs, and documentation updates. Since the previous review, the hook also stops storing raw non-Bash error text. All five previous Greptile threads are resolved; no new actionable issue was established.

Reviews (3) · Last reviewed commit: "fix(claude-code): store error size, not ..."

diazMelgarejo and others added 6 commits September 26, 2026 23:10
…wed sets

normalize_loop_event() in .agent/tools/data_layer_export.py copies
entry["event"], entry["status"], and entry["decision"] straight into
the exported "action" and "result" fields with no validation. Those
three keys are supposed to come from a small, supervisor-controlled
finite set -- harness_manager/loops/runner.py only ever writes 10
distinct event names, 10 status names, and 3 decision names to
runtime/loops/events.jsonl -- but nothing enforced that on the export
path, so any row with an unexpected value for those fields (a bug
upstream, a hand-edited events.jsonl, or a future loop kind that
doesn't yet exist) would flow the raw string straight through into
the dashboard/analytics surface.

That matters more here than it would in a generic ETL script: this
repo's own design intent for the loop event journal is that it's
content-free by construction. task/prompt/command/output are excluded
from events.jsonl entirely for exactly this reason -- see the existing
test_exports_privacy_safe_loop_events_and_quality_counts coverage,
which already asserts a "task" field never makes it into the export.
event/status/decision were the one gap in that whitelist discipline:
a closed, small vocabulary in the writer, treated as open text on the
read side.

Add VALID_LOOP_EVENTS/VALID_LOOP_STATUSES/VALID_LOOP_DECISIONS (mirrored
directly from runner.py's own literals) and a small _allowed_or_unknown()
helper. Anything outside the allowed set redacts to "unknown" -- the
same fallback already used everywhere else in this file for missing or
unrecognized values, so this isn't introducing a new convention, just
applying the existing one to a field that was skipped.

Adds one regression test alongside the existing loop-event privacy
test, verified to fail against the pre-fix code (a script tag and a
free-text string both land in the exported JSONL verbatim without the
fix) and pass with it.

(cherry picked from commit 5a2724e)
call_model()'s MiniMax path silently dropped the caller's max_tokens
entirely on the OpenAI-compatible wire -- the chat.completions.create()
call passed model/temperature/messages but nothing for output length.
Every MiniMax call through this wire has been unbounded regardless of
what max_tokens the caller specified.

The MiniMax OpenAI-compatible Chat Completions API deprecated bare
max_tokens in favor of max_completion_tokens; add
max_completion_tokens=max_tokens to close the gap. The Anthropic wire
is untouched -- it already forwards max_tokens correctly and Anthropic's
API keeps that parameter name.

Extends the existing test_call_model_openai_wire_global coverage with
an assertion on the new kwarg. Verified in both directions: fails with
KeyError against the pre-fix code, passes with the fix.

(cherry picked from commit 7b88fcd)
_load_learn() stubs sys.modules["text"]/["cluster"] to isolate
learn.py from its two sibling modules, but never removed the stubs
after loading -- they stayed process-wide for the rest of the test
run. Verified this is a real leak, not a theoretical one: after
calling _load_learn() once, both "text" and "cluster" remain in
sys.modules; any later test or import in the same process would
silently get the throwaway lambda stand-ins instead of the real
modules. Save whatever was previously in sys.modules for those two
names (or note there was nothing) before stubbing, and restore that
exact prior state in a finally block around exec_module() so the
stubs never outlive the one learn.py load they exist for.

Also adds encoding="utf-8" to the one read_text() call in this file
that was using the platform default encoding, per this repo's own
"force UTF-8 when reading/writing tracked text" convention (already
followed everywhere else _episodic() and learn.py itself read files).

The third test in this file, test_append_mirror_fails_open_on_write_error,
is untouched: _append_episodic_mirror()'s fail-open behavior on a
write error is this file's own explicit, documented design (its
docstring says so directly, matching _lesson_already_appended's
read-only fail-open posture) -- not a bug.

(cherry picked from commit 20441c2)
claude_code_post_tool.py persisted raw absolute paths and raw edit/write
content into AGENT_LEARNINGS.jsonl -- a log meant to be shared, diffed,
and exported via the data flywheel:

- file_path values went into action/reflection unnormalized, baking the
  operator's home directory into every entry that touched a file.
- Edit/MultiEdit reflection used repr(old[:30])/repr(new[:30]) -- literal
  content excerpts, not just that an edit happened.
- The generic _reflection() fallback (for tool types not explicitly
  handled -- Read, Grep, Glob, WebFetch, etc.) embedded raw
  json.dumps(tool_input) whenever it was under 80 chars, which is
  backwards: a short absolute path is exactly the case most likely to
  leak in full.

Adds _normalize_path(), reusing the hook's own existing AGENT_ROOT
resolution (no new dependency, no new config) to normalize to
project-relative or ~-relative form. Replaces content/diff previews with
char counts. Fixes the _reflection() fallback to normalize paths before
its length check instead of dumping raw JSON.

tests/test_claude_code_hook.py: new TestNoRawContentOrPathsPersisted-style
coverage (8 new checks, section 10b) asserting no raw home path or file
content survives into a persisted entry, for both Edit and Write --
62/62 passing, up from 54/54.

Fixes #66

(cherry picked from commit 81e0f1e)
The loop allowlist added in the previous commit missed three values the
supervisor really writes: runner.py uses the run status as the event name
on a breaker stop ("exhausted"), and process.py reports maker results as
"failed_to_start" or "timed_out". Those rows were exported as
"unknown". Add them and cover them in the redaction test.
README: list all 15 seed skills (brain + four loop skills), add loops/,
Copilot/Pi hooks and tests/ to the repo layout, correct the Windsurf rule
path, move install notes out of the Brain section, and drop the duplicate
changelog section. CHANGELOG: add an Unreleased entry for #64, #65, #67.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T17:47:22.754187Z 766b34e PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread .agent/harness/llm.py Outdated

@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: 766b34e312

ℹ️ 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 on lines +462 to +466
_fallback_input = dict(tool_input) if isinstance(tool_input, dict) else {}
for _key in ("file_path", "path", "new_path"):
if isinstance(_fallback_input.get(_key), str):
_fallback_input[_key] = _normalize_path(_fallback_input[_key])
inp_str = json.dumps(_fallback_input)

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 Redact fallback tool inputs before persisting them

When the hook receives a tool without an explicit branch—such as Claude Code's NotebookEdit—and its serialized input is under 80 characters, the fallback reflection persists this entire dictionary. Only file_path, path, and new_path are normalized, so inputs such as {"notebook_path":"/home/alice/x.ipynb","new_source":"API_KEY=secret"} retain both the absolute home path and raw content in episodic memory, which may later be committed or exported. Replace the fallback serialization with whitelisted metadata or explicitly redact content-bearing and path fields.

Useful? React with 👍 / 👎.

Comment thread .agent/harness/hooks/claude_code_post_tool.py Outdated
Comment thread .agent/harness/hooks/claude_code_post_tool.py Outdated
Comment thread .agent/tools/data_layer_export.py Outdated
Comment thread README.md Outdated
- claude_code_post_tool: the fallback reflection no longer dumps short
  tool inputs (NotebookEdit notebook_path/new_source leaked); it records
  the normalized path and input key names. Non-Bash detail stores output
  size instead of the first 150 chars of output (Read of a secrets file
  leaked), plus the first error line on failure. Paths outside both the
  project and home become <external>, not ../../<user>/...
- llm: MiniMax OpenAI wire uses max_tokens; max_completion_tokens does
  not exist in the minimum supported openai==1.40.0 and raised TypeError.
- data_layer_export: allow phase_started/running from the desktop
  supervisor.
- README/CHANGELOG: 14 seed skills, not 15.

Every new check fails on the previous code and passes now.
Comment thread .agent/harness/hooks/claude_code_post_tool.py
Comment thread .agent/harness/hooks/claude_code_post_tool.py Outdated
Failed non-Bash tool calls appended the first 150 chars of the error to
the episodic entry, so an error echoing a path or file content bypassed
the metadata-only detail. Store error_chars instead. The new check fails
on the previous code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Privacy: claude_code_post_tool.py persists raw absolute paths and file content/diffs into AGENT_LEARNINGS.jsonl

2 participants