Skip to content

v4.9.0 — non-Bash error detection, verified scrubbing, restored diagnostics - #31

Merged
Luispitik merged 5 commits into
mainfrom
release/v4.9.0
Jul 27, 2026
Merged

v4.9.0 — non-Bash error detection, verified scrubbing, restored diagnostics#31
Luispitik merged 5 commits into
mainfrom
release/v4.9.0

Conversation

@Luispitik

Copy link
Copy Markdown
Owner

Release v4.9.0. Two contributed PRs plus the debt they surfaced.

Contributions

@juanparisma#29, learner false positives. The learner scanned file content for error words, so every file that merely contained the string "error" became a proposal. Verified over a real corpus: 1,074 flags down to 22. His commit rides in this branch, so #29 closes as merged when this lands.

@Sergio-LPA#22, cross-OS registry (already on main, c072380). The same project seen from macOS and from Windows produced two registry entries. His fix collapses them by remote, with a name-key fallback guarded so two same-named projects on one machine stay apart, plus a migration pass that heals registries written before the feature. Two months of dogfooding across Mac and PC before he opened it, and he took two rounds of review with weeks in between without complaint.

The debt those two surfaced

Non-Bash tool failures were flagged by nobody. #29 was right to stop scanning payloads, but restricting hard markers to Bash left real Edit/Read failures undetected. Detection now keys on shape: a failure returns a bare verdict string, a success returns a payload serialising from { or [, and payloads are never scanned — that scan is the bug #29 exists to fix. Zero false positives over 7,931 real non-Bash observations.

The learner threw away its own diagnostics. The node -e block ended in 2>/dev/null, which swallowed lock-contention and failed-upsert messages and overrode the SINAPSIS_DEBUG redirect — so the debug switch produced nothing at all for the 95% of the script living inside that block. Diagnostics now append to _session-learner.log. @Sergio-LPA independently found this while working on #22 and offered to send a PR; it was already in flight here.

Secret scrubbing had never actually been tested. Test Group 4 imported a script that cannot be imported, then looked for a function that does not exist. Every assertion returned LOAD_FAIL and printed as SKIP, so the headline "11/11 passed" covered nothing. It now drives the hook through the front door — feed it a payload carrying a secret, assert what reached disk — across five formats, with a control proving the scrubber is not simply destroying all output.

Housekeeping

Token-shaped test fixtures are assembled at runtime instead of written out whole. None of them was ever a live credential and two are the vendors' own published examples, but a scanner cannot tell a fixture from a leak: written out whole they block the push and alarm anyone who greps the tree. What reaches the observer is byte-identical.

Two hardcoded personal paths went with them. commands/dashboard-sinapsis.md told every user to run a script under a home directory that is not theirs, via a path that exists on no installation at all — the installer lays the generator down as ~/.claude/skills/_generate-dashboard.py.

Tests

217 across 16 files, all registered in CI.

juanparisma and others added 5 commits July 16, 2026 00:16
…etection, by-design file exclusions, workflow trigram filter

One real session produced 190 proposals of which 189 were junk. Three
root causes, all fixed:

1. observe_v3.py flagged is_error=True whenever the output text contained
   "error"/"failed" keywords — including inside the CONTENT of successful
   Reads (e.g. source code with 'throw new Error'). Proof: err_msg was
   identical to sample_output on the junk proposals. Now detection is
   (a) structural harness failure shapes for every tool (dict error field,
   tool_use_error marker, start-of-output Error/InputValidationError) and
   (b) hard failure markers (Permission denied, command not found,
   Traceback, npm ERR!, ...) only for Bash, whose output is a run result
   rather than arbitrary file content.

2. user_correction counted any file edited 3+ times, but journals and
   control panels (hot.md, brief.md, working-memory.md, MEMORY.md,
   CLAUDE.md, daily summaries, wiki dirs) are re-edited BY DESIGN during
   end-of-day flows. They are now excluded via BYDESIGN_RE.

3. workflow_chain proposed every tool trigram repeated 2+ times, which is
   dominated by generic coding activity (Bash>Bash>Bash x380 in one
   session, 154 junk proposals). Trigrams now require 5+ repeats, >= 2
   distinct tools, and at least one non-generic (MCP/custom) tool.

The learner also re-validates the legacy is_error flag against hard
markers (isRealError) because observations written by older observers
remain contaminated, and PATTERN 5 (agent errors) uses the same
validation instead of keyword sniffing.

A/B on a real 8000-line observation window: 134 proposals with the old
code -> 17 with this fix, and the 17 survivors are all legitimate signals
(3 genuine tool failures, 7 genuinely iterated source files, 7 repeated
MCP workflow sequences).

Co-Authored-By: Claude Fable 5 <[email protected]>
…e diagnostics

Rounds out the two contributed PRs (#29 juanparisma, #22 Sergio-LPA) and clears
the debt they surfaced.

Non-Bash tool failures were flagged by nobody. #29 correctly stopped scanning
file CONTENT for error words, but restricting hard markers to Bash left real
Edit/Read failures ("String to replace not found", "File does not exist")
undetected — they are not harness-shaped either, so the structural branch also
missed them. Detection now keys on shape: a failure returns a bare verdict
string, a success returns a payload serialising from { or [, and payloads are
never scanned — that scan is the bug #29 exists to fix. Bounded by length as
well. Zero false positives over 7,931 real non-Bash observations.

The learner discarded its own diagnostics. The node block ended in 2>/dev/null,
swallowing lock-contention and failed-upsert messages and overriding the
SINAPSIS_DEBUG redirect, so the debug switch produced nothing for the 95% of the
script inside that block. Diagnostics append to _session-learner.log.

Secret scrubbing had never been tested. Test Group 4 imported a script that
cannot be imported, then looked for a function that does not exist; every
assertion returned LOAD_FAIL and printed as SKIP, so "11/11 passed" covered
nothing. It now feeds the hook a real secret and asserts what reached disk,
across five formats, with a control proving the scrubber is not simply
destroying all output.

The fixtures are synthetic — nothing here was ever a live credential, and two
of them are the vendors' own published examples — but each prefix is assembled
at runtime so no complete token-shaped literal lands in the tree. A scanner
cannot tell a fixture from a leak: written out whole, these block the push and
scare anyone who greps the repo. What reaches the observer is byte-identical.

New tests/test-error-detection.sh (10 tests) pins both halves of the fix.
Documented for future test authors: the observer resolves its config dir via
expanduser("~"), which on Windows reads USERPROFILE, not HOME — redirecting HOME
alone leaks synthetic observations into real learning data.

Docs: CHANGELOG v4.9.0, README header/badges/What's New, installer banners, and
FEATURES notes on the two accepted limits of the cross-OS key (posix collapses
macOS with Linux; the name key can fuse unrelated same-named projects, which is
why it now stamps and logs).

Suite: 217 tests across 16 files, all registered in CI.

Co-Authored-By: Claude Opus 5 <[email protected]>
commands/dashboard-sinapsis.md told every user to run a script under a home
directory that is not theirs, via a path that exists on no installation at all:
the installer lays the generator down as ~/.claude/skills/_generate-dashboard.py
and the template beside it as _dashboard-template.html, not under any hooks/
folder. The command now matches the installer and the convention the other
commands already follow.

The Windows-path fixture in test-eod-gather.sh loses a real username it never
needed; the assertion only ever read the basename.

Co-Authored-By: Claude Opus 5 <[email protected]>
@Luispitik
Luispitik merged commit 8163cbd into main Jul 27, 2026
3 checks passed
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.

2 participants