Skip to content

docs(privacy): say which captured calls keep nothing, and what Write and MultiEdit copy (#737, slice 5) - #976

Merged
github-actions[bot] merged 3 commits into
mainfrom
fix/737-capture-disclosure-details
Nov 10, 2026
Merged

github-actions[bot] merged 3 commits into
mainfrom
fix/737-capture-disclosure-details

Conversation

@cdeust

@cdeust cdeust commented Nov 10, 2026

Copy link
Copy Markdown
Owner

Summary

Tranche 5 de #737 (proposition 4, documentation), reprise des trois précisions notées après la revue 1 de #968. Chacune est lue dans mcp_server/hooks/post_tool_capture.py sur main (45da362) :

  • _should_capture renvoie output_too_short pour Edit, Write, MultiEdit, NotebookEdit et Bash quand la réponse normalisée fait moins de 50 caractères (_MIN_OUTPUT_LENGTH) : rien n'est construit, la ligne de commande non plus. PRIVACY.md disait qu'un motif tapé dans une commande est copié « même quand la commande n'affiche rien d'utile », ce qui est faux sous ce seuil ; la phrase porte maintenant sur une commande capturée.
  • _normalize_output ne garde d'une réponse portant oldString/newString (Edit) que le chemin et les chaînes avant/après ; toute autre réponse dict (Write, MultiEdit) passe par json.dumps entière, contenu écrit et ancien fichier compris quand l'hôte les envoie.
  • Un outil hors des trois listes (Task, TodoWrite, outils MCP) tombe dans low_value_tool et n'est jamais capturé.

Refs #737 (pas Closes : balayage des artefacts orphelins, exclusion par projet, purge par répertoire et nettoyeur optionnel restent ouverts et demandent une décision, voir l'issue).

Type of change

  • Documentation only

Test plan

  • tests_py/hooks/test_capture_privacy_disclosure.py : 4 tests ajoutés (8 cas avec la paramétrisation), chacun lit sa phrase dans PRIVACY.md et passe un vrai événement à process_event : réponse courte non capturée, Edit sans originalFile, Write/MultiEdit en JSON entier, Task/TodoWrite/outil MCP jamais capturés.
  • Mutations du hook (vérifiées puis annulées) : capturer les outils hors liste → 3 échecs ; supprimer le seuil de 50 caractères → 1 échec.
  • pytest tests_py/hooks/test_capture_privacy_disclosure.py tests_py/hooks/test_post_tool_capture.py : 28 passed.
  • ruff check et ruff format --check sur le fichier de test : OK. python scripts/check_doc_claims.py : OK.

Audit notes

  • python scripts/check_craftsmanship.py échoue localement sur [decision-integrity] invalid project wiki mirror, à l'identique sur main 45da362 sans ce diff : ce n'est pas lié à cette PR, et la CI dira si c'est propre à cet environnement.
  • Non vérifié : la suite complète et pyright (uv sync --locked impossible ici : téléchargement de torch refusé par le proxy) ; tests lancés dans un venv avec pydantic, pydantic-settings, numpy et pytest installés à la main.
  • Non vérifié : la forme exacte des réponses Write/MultiEdit envoyées par Claude Code (originalFile) ; le texte décrit ce que fait le hook avec toute réponse sans oldString/newString.

Reviewer checklist

  • CHANGELOG.md mis à jour (### Changed).
  • Documentation mise à jour (PRIVACY.md).
  • Pas de secret ni de donnée personnelle réelle (noms d'exemple fictifs).

Generated by Claude Code

…and MultiEdit copy (#737, slice 5)

PRIVACY.md said a Bash command line is copied "even when the command prints
nothing useful", but process_event drops a write-class or Bash call whose
normalized response is under _MIN_OUTPUT_LENGTH (50) before anything is built,
command line included. It also did not say that _normalize_output keeps only
the before/after strings of an Edit, while a Write or MultiEdit response (no
oldString/newString) is stored as its whole JSON, nor that a tool in none of
the three lists is dropped as low_value_tool.

Four tests drive the real process_event for each sentence.

Refs #737

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtuteev5XnAE8yMDVY
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-10T13:43:52.640076Z 8ae887c New commits
🔒 Security Review ✅ Completed 2026-11-10T13:44:52.104686Z 8ae887c New commits
ℹ️ 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.

@claude

claude Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 2m 26s —— View job


Review of PR #976 (review 1 of 3)

1 Blocker: PRIVACY.md:22-25 says a call whose response is under 50 characters is not captured at all. The hook measures the formatted output (_normalize_output), not what the tool returned:

  • A Bash command that prints 30 characters reaches 50 once the formatting is added, so it is captured with its command line.
  • A one-character Edit is about 64 characters once formatted, so it is captured with its path.

The new text therefore understates what is stored. The new test sends the plain string "ok", so it does not catch this.

3 Notes, no action before merge:

  • NotebookEdit is also stored as its whole JSON, but the text names only Write and MultiEdit.
  • The gist fallback when an artifact write fails is not mentioned; that text predates this PR.
  • The MultiEdit test sends a response shaped like a Write response.

Verdict: block

@claude

claude Bot commented Nov 10, 2026

Copy link
Copy Markdown

Review 1 of 3: PR #976

Blocker

  1. PRIVACY.md:22-25: the new sentence says a short call is not captured, but small calls are captured. It reads: "A call whose response is shorter is not captured at all, its path or command line included." The CHANGELOG entry (CHANGELOG.md:159-160) says the same. The 50-character check (_should_capture, mcp_server/hooks/post_tool_capture.py:70) is applied to the output of _normalize_output, which already includes the hook's own formatting, not to what the tool returned. I traced two cases through the code:

    • Bash: {"stdout": "x"*30} becomes **stdout:**\n\``\n…\n````, which is 20 characters of formatting plus 30 = 50. The call is captured with its command line, even though the command printed only 30 characters.
    • Edit: {"filePath": "/p/a.py", "oldString": "x", "newString": "y"} becomes **file:** plus **before:** and **after:** blocks, about 64 characters. A one-character edit is captured, path included. In practice the threshold almost never skips an Edit, Write or MultiEdit; their responses as JSON are longer still.

    A user reading the new sentence would think short outputs and small edits leave nothing behind. That understates what is stored, which is the error Auto-capture copies third-party personal data from any project, with no per-project opt-out, and can leave orphan artifacts #737 is meant to remove. The new test (test_capture_privacy_disclosure.py:126-129) sends a plain string "ok", so it does not see the formatting. Fix: say that the 50 characters count the formatted output (stdout with its labels, or the Edit path with before/after), and that in practice it only drops Bash calls that print almost nothing. Then add a test with {"stdout": "x" * 30} that expects a capture, and one with a one-character Edit.

Notes, no action before merge

  • NotebookEdit responses also go through json.dumps whole (no oldString/newString), but PRIVACY.md:26-28 names only Write and MultiEdit.
  • If writing the artifact fails, _gist_or_full (post_tool_capture.py:164-166) keeps the full response in the row, so "stored as a short gist" (PRIVACY.md:38-39) doesn't hold in that case. This text is not new in this PR.
  • The MultiEdit case at test_capture_privacy_disclosure.py:148-153 sends a Write-shaped response (content), not a MultiEdit one (edits).

Everything else checks out against the code in every CORTEX_CAPTURE_MODE: Edit keeps only before/after, originalFile is dropped; Write and MultiEdit keep the whole JSON; any tool outside the three lists is never captured. I couldn't run pytest or ruff here because the commands needed an approval this runner doesn't grant; CI will report them.

View job run
Verdict: block

@claude

claude Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 2m 30s —— View job


Review of PR #976 (review 2 of 3)

1 Blocker: PRIVACY.md:27-29 (and the CHANGELOG entry) says a Bash call that prints fewer than 30 characters on a single stream is dropped. That is false in two cases, and in both the call is captured with its command line:

  • Both streams print. stdout "ok" plus stderr "warn: x" comes to 51 characters once formatted.
  • The call is interrupted. The **interrupted:** True marker adds 21 characters, so 8 characters of stdout is enough.

2 Notes, no action before merge:

  • The test hardcodes "30" in its assert.
  • The exact 30-character boundary is tested on stdout only.

Verdict: block

@claude

claude Bot commented Nov 10, 2026

Copy link
Copy Markdown

Review 2 of 3: PR #976

The review 1 Blocker is fixed in fa41cf5. The 50-character check is now described as counting the formatted output, and the one-character Edit and the 29/30-character Bash cases are tested. I checked them against _normalize_output (mcp_server/hooks/post_tool_capture.py:209-261): a single stream gives 20 + n ≥ 50, and an empty call gives _(no output)_, which is 13 characters. The NotebookEdit wording and the shape of the MultiEdit test from review 1 are also fixed. This review covers fa41cf5 only.

Blocker

  1. PRIVACY.md:27-29 (and CHANGELOG.md:160-162): "A Bash call is dropped, command line included, when it prints … fewer than 30 characters on a single stream" is false in two cases, so the text still understates what is stored. _normalize_output adds up everything it formats before the 50-character check (post_tool_capture.py:232-241):

    • Both streams print. stdout "ok" makes 22 characters and stderr "warn: x" makes 27. With the \n\n separator the total is 51, so the call is captured with its command line, even though neither stream reached 30. Commands that write to stderr, such as git, often do this.
    • The call is interrupted. **interrupted:** True adds 21 characters. {"stdout": "12345678", "interrupted": true} makes 28 + 2 + 21 = 51, so it is captured.

    Fix: say the call is kept once its stdout, stderr and interrupted marker, as formatted, reach 50 characters. Then give "about 30 characters when only stdout or only stderr prints and the call is not interrupted" as the example, not the rule. In the test, add a case with both streams ({"stdout": "ok", "stderr": "warn: x"} is captured) and an interrupted case.

Notes, no action before merge

  • test_capture_privacy_disclosure.py:127 hardcodes "30" in the assert, so _number_before only adds a check on the format.
  • The exact 30-character boundary is tested on stdout only, so a different stderr label would go unnoticed.

I read the code and traced the tests by hand. Running pytest and ruff needed an approval this runner doesn't grant, so CI will report them. Five specialist reviews ran: docs, tests, quality, security and performance. The quality and performance reviews found nothing that would fail CI.

View job run
Verdict: block

@claude

claude Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 2m 18s —— View job


Review of PR #976 (review 3 of 3)

Blockers: none. The new Bash wording in PRIVACY.md matches the hook in every case I traced, and no case stores more than the text says. Every assertion in the new tests holds when I trace it by hand.

3 Notes, no action before merge:

  • The CHANGELOG parenthetical "(one stream alone: 30 characters)" doesn't say the call must not be interrupted, or that trailing whitespace is removed first.
  • That CHANGELOG line is too long and should be rewrapped.
  • The two-streams test has no case where the call is dropped.

Accepted: the whole PR (b9058b0, fa41cf5, 8ae887c).

Verdict: approve

@claude

claude Bot commented Nov 10, 2026

Copy link
Copy Markdown

Review 3 of 3: PR #976

The review 2 Blocker is fixed in 8ae887c. PRIVACY.md:27-32 no longer says a short Bash call is dropped. It now says a Bash call is kept once its stdout, stderr and interrupted marker, as formatted, add up to 50 characters. I checked each claim against _normalize_output and _should_capture (mcp_server/hooks/post_tool_capture.py:62-83, 209-261):

  • Prints nothing: the call is always dropped. _(no output)_ is 13 characters, and an interrupted marker with no output is 21.
  • One stream, not interrupted: the call is dropped below 30 characters, because 20 characters of formatting are added to the output.
  • Two streams or the marker: these reach 50 with less output. For example, "ok" plus "warn: x" comes to 51, and 8 characters plus the marker comes to 51.

The new wording doesn't understate what is stored in any case. I traced the new tests by hand: every assertion holds and none passes vacuously, and the two cases from review 2 are now covered. This review covers 8ae887c only.

Blockers: none.

Notes, no action before merge

  • CHANGELOG.md:162 "(one stream alone: 30 characters)" doesn't say the call must not be interrupted, or that trailing whitespace is stripped before counting. PRIVACY.md says this precisely.
  • CHANGELOG.md:162 is longer than the lines around it; it should be rewrapped.
  • The two-streams test has no negative case, for example two streams adding up to 49 that is dropped.

Accepted: the whole PR (b9058b0, fa41cf5, 8ae887c). No file or commit carries an open Blocker.

Five specialist reviews ran: docs, tests, quality, security and performance. The quality review found nothing that would fail ruff or the craftsmanship gate, and the performance review found nothing. I couldn't run pytest or ruff here, because the commands needed an approval this runner doesn't grant, so CI will report them.

View job run
Verdict: approve

@github-actions
github-actions Bot merged commit 41f55cc into main Nov 10, 2026
18 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