Skip to content

Capture followthrough policy and test-harness isolation - #31

Merged
flyingrobots merged 20 commits into
mainfrom
split/capture-and-test-isolation
Aug 4, 2026
Merged

Capture followthrough policy and test-harness isolation#31
flyingrobots merged 20 commits into
mainfrom
split/capture-and-test-isolation

Conversation

@flyingrobots

Copy link
Copy Markdown
Owner

Split out of #30 so the fixes that repair things broken on main today can land on their own, without waiting behind the MCP install tooling.

Every commit here is cherry-picked from #30 with its original message intact, so each fix keeps the reproduction that found it. Verified as a clean subset: all 16 code and doc files are byte-identical to their #30 counterparts. No install tooling, no README rewrite, no git-cas fixture, no package.json change.

What this fixes on main

The pre-push hook cannot currently be satisfied. Git exports GIT_DIR and friends to every hook, so tests that shell out to git resolved the hook's repository instead of their own fixture — execSync('git init', { cwd: tempDir }) wrote to $GIT_DIR. Six tests passed under npm run test:fast and failed under git push. The scrub now queries git rev-parse --local-env-vars rather than a hand-maintained list, in both the shell hook and the JS fixtures, case-insensitively.

The acceptance suite could write into a developer's real mind. Fixtures pinned HOME but inherited THINK_REPO_DIR, which getLocalRepoDir honours ahead of $HOME/.think/repo. Reproduced: running test/acceptance/mcp.test.js with it exported wrote 21 commits of test captures into a sentinel directory. Multi-agent setups export exactly that variable to namespace their minds, so this was the normal case.

The followthrough deferral was not a guarantee. The timer was unref'd, so a process with nothing else pending exited instead of deferring — reproduced standalone as exit code 13, "unsettled top-level await". In node:test that took a whole file down as cancelledByParent.

The budget could be spent twice. The CLI awaits twice and handed each the full budget, so a slow capture took up to 2× what was configured — measured 3506ms against a 2000ms budget, now 2835ms. An exhausted budget also defers immediately rather than racing a zero-delay timer.

The 6s budget was duplicated between the CLI and MCP surfaces, which compared the deferral sentinel differently. Now one owner in src/capture-followthrough.js, overridable via THINK_CAPTURE_FOLLOWTHROUGH_TIMEOUT_MS for cold repositories, clamped to the 32-bit timer range so a large value cannot collapse to Node's 1ms substitute.

A latent CI failure in the existing gemini fixture test. Its guard only checked for the git-warp migration, so a checkout without refs/cas/* would have failed on an unreachable object id rather than skipping. It stayed hidden because CI's npm test was failing earlier in test:ports.

Also corrected

The documented cost of a deferred capture. An earlier claim that remember still finds it was drawn from a single unrepeated observation and is wrong: which surfaces can see it is nondeterministic, because the abandoned followthrough keeps running. The underlying read-model corruption is logged as a bad-code backlog item with a full reproduction, and confirmed pre-existing on main by forcing its hardcoded budget to 1ms.

Verification

Gate Result
npm run test:fast 183 pass, 0 fail, standing alone off origin/main
Runtime Truth ratchet passed, no baseline bump
Hexagonal Boundary ratchet passed, no baseline bump
Subset check 16/16 files byte-identical to #30
Full test:m1 running locally; CI covers the same suite here

Note

One commit removes a node_modules symlink I committed by mistake. .gitignore lists node_modules/ — with a trailing slash that matches a directory, not a symlink of the same name, so git add -A in a worktree that symlinks its dependencies picks it up. Removed in a follow-up commit rather than an amend.

#30 stays open with the MCP install tooling, README rewrite and git-cas fixture, currently at zero unresolved threads.

flyingrobots and others added 19 commits August 4, 2026 02:44
The 6s followthrough budget and the waitForCaptureFollowthrough helper
were duplicated verbatim in src/cli/commands/capture.js and
src/mcp/service.js, so the two capture surfaces could drift apart. The
CLI compared the deferral sentinel by object identity while MCP compared
it by status, and the CLI reported a hardcoded timeoutMs in its
capture.followthrough.deferred event.

Extract src/capture-followthrough.js as the single owner of the budget,
the frozen deferral sentinel, the deferral predicate, and the race. Both
surfaces now import it, and the CLI reports the budget it actually used.

The budget becomes overridable via THINK_CAPTURE_FOLLOWTHROUGH_TIMEOUT_MS
so operators on cold repositories or slow filesystems can raise it. An
unusable value falls back to the default instead of throwing, because
capture is a trapdoor and must not fail on a malformed knob.
The acceptance fixtures pinned HOME to a temp dir but inherited the rest
of the environment. getLocalRepoDir honours THINK_REPO_DIR *ahead* of
$HOME/.think/repo, so a developer with THINK_REPO_DIR exported ran the
whole suite against their real mind. Multi-agent setups export exactly
that variable to namespace minds, so this was the normal case.

Reproduced before the fix: running test/acceptance/mcp.test.js with
THINK_REPO_DIR pointed at a sentinel directory wrote 21 commits of test
captures into it, and cross-contamination also failed one assertion.
After the fix the same run leaves the sentinel empty and passes 6/6.

THINK_PROMPT_METRICS_FILE and the THINK_CAPTURE_* provenance variables
bypassed HOME the same way. Add createHermeticThinkEnv/scrubThinkEnv,
which drop every inherited THINK_ variable before layering the fixture's
own values back on. The scrub is prefix-wide rather than an allowlist so
a newly added knob cannot silently reopen the hole; baseEnv and extraEnv
still layer on top, so tests that set THINK_ variables deliberately keep
working.

Covered by test/ports/test-isolation.test.js, which guards the harness
itself because a regression here silently corrupts real user data.
Asserting capture.warnings.length === 0 against the 6s production
default made a correctness assertion depend on wall-clock latency: it
passed in isolation but failed under the concurrent cold spawns of the
full acceptance suite (actual 1, expected 0).

Pin a generous 120s budget for these spawns so they observe the
completed-followthrough path deterministically. The assertion still
proves the healthy path; the deferral path stays covered without a wall
clock by the dependency-injected test in test/ports/mcp-service.test.js.

Retires docs/method/backlog/bad-code/
CORE_mcp-capture-warning-assertion-is-timing-dependent.md.
Node omits env pairs whose value is undefined, so createHermeticThinkEnv
called without homeDir produced a child with no HOME at all. getHomeDir
falls back to os.homedir(), which means the suite would quietly resolve
~/.think against the developer's real home and reopen the isolation hole
with no visible error.

Verified: spawning a child with HOME set to undefined leaves
process.env.HOME null while os.homedir() still returns the real home.

Require homeDir, and normalise a missing upstreamUrl to '' so an
inherited THINK_UPSTREAM_URL cannot survive the scrub either.
The CLI reports the budget it used in capture.followthrough.deferred,
but the MCP warning was a fixed string. An agent receiving it could not
tell what the budget was, that the budget is configurable, that the
thought is still recallable through remember, or that retrying would
duplicate the thought.

Resolve the budget through the injected dependency set and name it in
the warning, along with the surfaces that will not see the capture yet
and the knob that raises the budget.
Git exports GIT_DIR and friends to every hook, so the suite launched by
scripts/hooks/pre-push inherited them. Tests that shell out to git then
resolved the hook's repository instead of their own fixture:
`execSync('git init', { cwd: tempDir })` wrote to $GIT_DIR, so the
fixture repo was never created and discoverMinds found nothing.

The effect was that `npm run test:fast` passed but `git push` failed on
six tests, which made the pre-push hook impossible to satisfy — the
suite could never gate a push.

Unset the repository-location variables in both hooks via a shared
scripts/hooks/lib/scrub-git-env.sh, and scrub the same set in
createHermeticThinkEnv so spawned Think processes cannot be redirected
either. Identity and transport variables are left alone: the product
sets its own commit identity and inheriting GIT_SSH_COMMAND is fine.

Verified: with GIT_DIR and GIT_WORK_TREE set, the two affected test
files go from 7/13 to 13/13.
The set of git repository-location variables to scrub is expressed twice
— in JavaScript for spawned Think processes and in shell for the hooks —
because hooks cannot import the module and the fixtures cannot source the
shell function. Nothing forced the two to agree, so adding a variable to
one list alone would silently leave the other execution path
redirectable. That is the same failure that made the pre-push hook
impossible to satisfy, reintroduced as latent drift by e67ea0e.

The defect was the missing enforcement rather than wrong code, so the fix
is the guard: parse the shell function and assert it unsets exactly the
variables GIT_LOCATION_ENV_VARS names, that it lists each once, that no
identity or transport variable creeps in, and that both hooks scrub
before invoking npm.

Verified by deleting `unset GIT_WORK_TREE` from the shell script: the
test fails with "js only: GIT_WORK_TREE" and passes once restored.
…ence

TECHNICAL-TEARDOWN.md enumerates Think's environment tuning and was the
canonical list, but it did not mention the followthrough budget this
branch adds. Record the default, that one module resolves it for both
capture surfaces, what deferral costs per read surface, and why an
unusable value falls back rather than failing the capture.

GUIDE.md only mentions THINK_UPSTREAM_URL in passing rather than
enumerating variables, so it needs no change.
setTimeout stores its delay in a 32-bit signed integer. Node substitutes
1ms for anything larger and emits TimeoutOverflowWarning, so a budget of
2_147_483_648 — which passed Number.isSafeInteger and the resolver — made
capture defer immediately, the exact opposite of asking for a long budget.

Clamp to MAX_CAPTURE_FOLLOWTHROUGH_TIMEOUT_MS in both the resolver and
waitForCaptureFollowthrough, since callers can supply a budget without
going through the resolver. Clamping rather than defaulting keeps the
direction the operator asked for instead of silently dropping to 6s.

Reported by CodeRabbit. Verified: an over-range budget no longer warns
and no longer defers immediately.
runCaptureFollowthrough awaits twice — the graph-model probe, then the
finalize — and handed each the full budget. A slow capture could therefore
spend up to twice what the operator configured, which defeats the point of
bounding it at all.

Measured before: 3506ms total against a 2000ms budget. After: 2835ms for
2000ms and 1641ms for 1000ms, i.e. one budget plus process overhead rather
than two budgets.

Introduce createCaptureFollowthroughDeadline and pass remainingMs() to
each wait, so the budget means total elapsed followthrough time. The
deferred event still reports the configured budget rather than whatever
remained, since that is the number the operator set.

Reported by CodeRabbit.
The parity guard added in 07c3eb3 asserted the shell and JavaScript lists
agreed. They did — and both were missing eight variables that git reports
as repository-local: GIT_CONFIG, GIT_CONFIG_COUNT, GIT_CONFIG_PARAMETERS,
GIT_GRAFT_FILE, GIT_IMPLICIT_WORK_TREE, GIT_NO_REPLACE_OBJECTS,
GIT_REPLACE_REF_BASE and GIT_SHALLOW_FILE. Agreement between two
incomplete lists proves nothing, which is precisely the critique.

Query `git rev-parse --local-env-vars` in both paths so the set tracks the
installed git, and keep only the four repository-scoping variables absent
from that list as a hand-maintained extra. Coverage went from 11 to 19.

Rewrite the guard to assert completeness against git's own answer rather
than mutual agreement, keep the extras in sync, and keep asserting that no
identity or transport variable is dropped.

Reported by CodeRabbit against my own guard. Verified: GIT_CONFIG and
GIT_CONFIG_COUNT are now cleared by the shell helper.
Windows environment lookup is case-insensitive, so an inherited
`Think_Repo_Dir` passed the case-sensitive filter yet was still returned
by process.env.THINK_REPO_DIR in the spawned child. The acceptance suite
could therefore be redirected into a developer's real mind on Windows even
after the scrub was added — the same data-loss hole, reachable through
casing alone.

Normalise keys to uppercase before testing both the THINK_ prefix and the
git location set.

Reported by Codex.
The deferral timer was unref'd, inherited from the duplicated
implementations this branch consolidated. An unref'd timer does not hold
the event loop, so a process with nothing else pending exits instead of
deferring — the budget stopped being a guarantee and the caller's await
never settled.

Caught by CI, which the local fast suite could not surface: node:test took
the whole file down as cancelledByParent, six tests reported not ok while
`fail 0` and `cancelled 6`, and the runner exited 1. Reproduced directly as
exit code 13, "unsettled top-level await", in a bare process; adding any
ref'd timer made the same call resolve correctly.

Remove the unref. clearTimeout in the finally already guarantees the timer
never outlives the race, so unref bought nothing but the early-exit hazard.

Measured no timing change in real captures — 4717/6815/6822ms before versus
4868/6792/6831ms after over three captures — because the git child
processes hold the loop there regardless. The regression test asserts the
deferral in a fresh child process, which has nothing else pending by
construction and therefore fails deterministically rather than under load.
… defect

I documented, as verified fact, that a deferred capture stays reachable
through `remember` while `recent` and `stats` under-report it. That was
drawn from a single unrepeated observation and it is wrong in both
directions. Eight controlled trials say:

- `remember` does NOT find it — 0 matches.
- `recent`/`stats` do not merely under-report; immediately after a deferral
  they report ONLY the deferred entry (1 instead of 4 in a four-capture
  mind), hiding every older capture.
- Once a later healthy capture rebuilds the read model, the deferred entry
  is dropped from both surfaces permanently.
- What actually survives is the raw layer: `inspect` returns exact text and
  identity. What is skipped is the derived layer —
  `canonicalThought.stored` false, `sessionAttribution` and `seedQuality`
  null.

Confirmed pre-existing on origin/main by forcing its hardcoded budget to
1ms: same recent=1, stats=1. Exposing the budget as an env knob only made
the state easy to reach on purpose. Logged with the full reproduction as a
bad-code item rather than fixed here, since the read-model change is
product work well outside this branch.

Correct README, TECHNICAL-TEARDOWN and the MCP deferral warning to the
verified behaviour, and update the two ports tests that asserted the old
wording.
The CLI's second await receives deadline.remainingMs(), which is 0 once the
budget is spent. waitForCaptureFollowthrough turned that into
setTimeout(0) and raced it, so an already-settled followthrough won on the
microtask queue and work slipped through a budget that had nothing left.

Return the deferral sentinel directly for a non-positive budget. A negative
budget is treated the same way rather than as an immediate timer.
`unset "${name}"` removes only the exact uppercase name, so an inherited
`Git_Dir` survived while git could still resolve the invoking repository
through it. The JavaScript side was made case-insensitive earlier; the
shell path was not, which left the isolation half-done.

Enumerate what is actually exported and unset every case-insensitive match,
with a hook-level regression test that runs the function against Git_Dir,
git_config and their uppercase forms.

Reported by CodeRabbit.
Seven items, none of which change product behaviour.

The over-range budget test proved nothing: an already-resolved followthrough
won on the microtask queue, so it passed even with the clamp removed.
Delaying the followthrough past a tick makes it observe the budgeted timer —
verified by deleting the clamp and watching it fail.

The child-process deferral test had no timeout, so a regression leaving the
child's await unsettled would hang the whole suite rather than fail one test.

test-isolation compared a raw key in one assertion and an uppercased key in
another, so a mixed-case fixture-owned variable counted as a leak in one and
not the other. Both now use one normalising helper.

repair-v17's guard skipped on any git failure, which would have hidden a bad
treeOid, a wrong object type or a failed git invocation behind the
"not pushed to the remote" reason. Only a missing object skips now.

The MCP deferral warning and the changelog said "until the next capture",
but a later capture can also defer, so neither guarantees recovery.

The CI-004 fenced block gained its language identifier.

The fixture test asserted nothing about deferred recall while the defect
record claimed zero matches and the planning note claimed one. Measured: this
archived mind returns one. The manifest now records rememberMatchCount as an
observation of that artifact, the test asserts it, and both the manifest note
and the backlog item say plainly that the disagreement between reproductions
is the defect rather than a contract.

Reported by CodeRabbit.
Scoped to this branch only. The install tooling, README rewrite and git-cas
fixture entries stay with the branch that carries them.
.gitignore lists `node_modules/` with a trailing slash, which matches a
directory but not a symlink of the same name, so a `git add -A` in a
worktree that symlinks its dependencies picked it up.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@flyingrobots, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 77ed8949-dd47-4181-85e1-746277947c35

📥 Commits

Reviewing files that changed from the base of the PR and between 66bb72b and f866f14.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • TECHNICAL-TEARDOWN.md
  • node_modules
  • src/capture-followthrough.js
  • src/mcp/service.js
  • test/acceptance/repair-v17-mind.test.js
  • test/ports/capture-followthrough.test.js
  • test/ports/hook-git-env-parity.test.js

Summary by CodeRabbit

  • New Features

    • Added configurable capture follow-through timeouts for CLI and MCP workflows.
    • Added safe handling for invalid or excessive timeout values.
    • Deferred captures now provide clearer warnings about processing delays and read-model availability.
  • Bug Fixes

    • Improved timeout and deferred-processing behavior across capture interfaces.
    • Acceptance and repository checks now run with cleaner, isolated environments.
  • Documentation

    • Documented timeout configuration, deferred-capture costs, and known read-model limitations.

Walkthrough

The change centralizes capture followthrough timeout handling across CLI and MCP paths. It adds configurable deadlines and deferred results, hermetic test environments, Git environment scrubbing for hooks, acceptance fixture checks, and related documentation.

Changes

Capture followthrough handling

Layer / File(s) Summary
Shared followthrough timing
src/capture-followthrough.js, test/ports/capture-followthrough.test.js
Adds timeout resolution, maximum clamping, shared deadlines, deferred-result detection, timer cleanup, rejection propagation, and comprehensive tests.
CLI and MCP integration
src/cli/commands/capture.js, src/mcp/service.js, test/ports/mcp-service.test.js, TECHNICAL-TEARDOWN.md, docs/method/backlog/bad-code/CORE_deferred-capture-corrupts-the-recent-read-model.md, CHANGELOG.md
CLI waits consume one overall budget. MCP uses the shared resolver and emits timeout-specific warnings. Documentation covers configuration and deferred read-model behavior.

Environment isolation

Layer / File(s) Summary
Hermetic test environments
test/fixtures/runtime.js, test/fixtures/think.js, test/ports/test-isolation.test.js
Test fixtures discover and scrub inherited Think and Git repository variables while enforcing HOME, upstream URL, timeout, and explicit overrides.
Git hook scrubbing
scripts/hooks/lib/scrub-git-env.sh, scripts/hooks/pre-commit, scripts/hooks/pre-push, test/ports/hook-git-env-parity.test.js
Hooks remove case-insensitive repository-scoping variables before checks. Tests compare shell and JavaScript scrubbing behavior and preserve identity and transport variables.
Acceptance isolation and fixture handling
test/acceptance/mcp.test.js, test/acceptance/repair-v17-mind.test.js, CHANGELOG.md
Acceptance processes use hermetic environments. CAS restoration skips missing objects and fails malformed or non-tree fixtures.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • flyingrobots/think#15: Overlaps in MCP and CLI followthrough timeout and deferred-result handling.
  • flyingrobots/think#28: Covers the same CLI and MCP timeout paths that this change centralizes and configures.
  • flyingrobots/think#30: Shares capture followthrough, environment scrubbing, fixture, and acceptance-test changes.

Poem

A deadline ticks, then yields deferred,
The CLI shares the time it heard.
MCP explains what waits behind,
Clean hooks leave stray state confined.
Hermetic tests now run aligned.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the two primary changes: centralized capture followthrough policy and improved test-harness isolation.
Description check ✅ Passed The description directly explains the fixes, scope, verification results, and excluded changes covered by the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@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: 66bb72bbba

ℹ️ 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/capture-followthrough.js Outdated
Comment thread test/acceptance/repair-v17-mind.test.js

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/capture-followthrough.js`:
- Around line 57-65: Update createCaptureFollowthroughDeadline in
src/capture-followthrough.js to use a monotonic time source for startedAt,
remainingMs, and expired so backward wall-clock changes cannot extend the
clamped budget. Add coverage in test/ports/capture-followthrough.test.js
covering backward clock movement and asserting that remainingMs does not
increase.
- Around line 35-46: The timeout resolver in
resolveCaptureFollowthroughTimeoutMs already falls back for values above
Number.MAX_SAFE_INTEGER; add an over-range assertion for this deterministic
behavior. In test/ports/capture-followthrough.test.js lines 43-55, extend the
“very large budget” test with an unsafe-decimal input and assert it returns
DEFAULT_CAPTURE_FOLLOWTHROUGH_TIMEOUT_MS; no direct source change is needed in
src/capture-followthrough.js.

In `@src/mcp/service.js`:
- Around line 47-54: Update buildCaptureFollowthroughDeferredWarning in
src/mcp/service.js to state that derived records may remain incomplete when the
response returns, without describing followthrough as cancelled or skipped. In
CHANGELOG.md lines 27-29, replace “skips” with non-cancellation terminology. In
TECHNICAL-TEARDOWN.md line 396, document that recovery can occur through either
late completion or a later healthy capture.

In `@test/acceptance/repair-v17-mind.test.js`:
- Around line 52-53: Update the git cat-file probe handling around the status
check so only a completed probe with exit status 0 and trimmed stdout equal to
“tree” is treated as a missing-object skip condition. For invalid object IDs,
aborted probes, repository errors, or any other non-tree result, raise an
assertion failure instead of returning false and allowing t.skip() to hide the
fixture failure.

In `@test/ports/hook-git-env-parity.test.js`:
- Around line 76-101: Update the shell-hook test around scrub_git_location_env
to keep the bash -c program static and pass SCRUB_SCRIPT as a positional
argument, then source that argument inside the script. Remove the interpolated
JSON.stringify(SCRUB_SCRIPT) from the generated command while preserving the
existing environment setup and assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c8df97f7-353a-41ff-9cf4-692065d28669

📥 Commits

Reviewing files that changed from the base of the PR and between 24e2613 and 66bb72b.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • TECHNICAL-TEARDOWN.md
  • docs/method/backlog/bad-code/CORE_deferred-capture-corrupts-the-recent-read-model.md
  • scripts/hooks/lib/scrub-git-env.sh
  • scripts/hooks/pre-commit
  • scripts/hooks/pre-push
  • src/capture-followthrough.js
  • src/cli/commands/capture.js
  • src/mcp/service.js
  • test/acceptance/mcp.test.js
  • test/acceptance/repair-v17-mind.test.js
  • test/fixtures/runtime.js
  • test/fixtures/think.js
  • test/ports/capture-followthrough.test.js
  • test/ports/hook-git-env-parity.test.js
  • test/ports/mcp-service.test.js
  • test/ports/test-isolation.test.js
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: test (22)
🧰 Additional context used
🪛 ast-grep (0.45.0)
test/fixtures/think.js

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawnSync } from 'node:child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawnSync } from 'node:child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

test/ports/capture-followthrough.test.js

[warning] 1-1: Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFileSync } from 'node:child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

test/ports/hook-git-env-parity.test.js

[warning] 1-1: Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFileSync, spawnSync } from 'node:child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

src/capture-followthrough.js

[warning] 96-96: Avoid using the initial state variable in setState
Context: setTimeout(() => resolve(CAPTURE_FOLLOWTHROUGH_DEFERRED), budgetMs)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

🪛 LanguageTool
TECHNICAL-TEARDOWN.md

[style] ~396-~396: Consider replacing this word to strengthen your wording.
Context: ... default, because capture is a trapdoor and must not fail on a malformed knob. `TH...

(AND_THAT)

docs/method/backlog/bad-code/CORE_deferred-capture-corrupts-the-recent-read-model.md

[style] ~67-~67: Consider an alternative for the overused word “exactly”.
Context: ... 6 seconds on its first write, which is exactly what made the MCP acceptance assertion ...

(EXACTLY_PRECISELY)

🪛 OpenGrep (1.26.0)
test/ports/hook-git-env-parity.test.js

[ERROR] 61-61: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🔇 Additional comments (14)
test/fixtures/runtime.js (1)

2-2: LGTM!

Also applies to: 20-118

test/fixtures/think.js (1)

4-4: LGTM!

Also applies to: 37-41

test/ports/test-isolation.test.js (1)

1-204: LGTM!

scripts/hooks/lib/scrub-git-env.sh (1)

1-43: LGTM!

scripts/hooks/pre-commit (1)

4-6: LGTM!

scripts/hooks/pre-push (1)

4-6: LGTM!

test/ports/hook-git-env-parity.test.js (1)

1-75: LGTM!

Also applies to: 103-136

src/capture-followthrough.js (1)

14-27: LGTM!

Also applies to: 68-105

test/ports/capture-followthrough.test.js (1)

15-41: LGTM!

Also applies to: 57-154, 185-196

src/cli/commands/capture.js (1)

2-7: LGTM!

Also applies to: 60-76, 95-104

src/mcp/service.js (1)

1-5: LGTM!

Also applies to: 62-76, 115-132

test/ports/mcp-service.test.js (1)

24-24: LGTM!

Also applies to: 34-58, 101-103

CHANGELOG.md (1)

15-26: LGTM!

Also applies to: 30-41

docs/method/backlog/bad-code/CORE_deferred-capture-corrupts-the-recent-read-model.md (1)

1-80: LGTM!

Comment thread src/capture-followthrough.js
Comment thread src/capture-followthrough.js Outdated
Comment thread src/mcp/service.js
Comment thread test/acceptance/repair-v17-mind.test.js
Comment thread test/ports/hook-git-env-parity.test.js
Five distinct issues; both reviewers converged on three of the areas.

A digit-only budget above Number.MAX_SAFE_INTEGER fell back to the 6s
default instead of clamping, which is the opposite of the documented
over-range behaviour and of what an operator asking for a huge budget
wants. Verified: 10000000000000000 returned 6000, now returns the timer
maximum. The value is already known to be digit-only, so imprecision in
parseInt is not a reason to treat it as unusable.

The deadline used an adjustable clock, so a backward jump increased
remainingMs and could stretch the second followthrough wait past the
budget. It now defaults to performance.now() and, because the source stays
injectable, keeps a high-water mark of elapsed time — merely clamping at
zero would have restored spent budget rather than held it.

The repair-v17 guard skipped on any non-zero git cat-file exit, so a
malformed manifest such as treeOid "not-an-oid" was reported as a missing
refs/cas/* object. Only a genuinely absent object may skip now; a bad oid
or a failed probe asserts.

The MCP deferral warning, TECHNICAL-TEARDOWN and the changelog described
the followthrough as skipped. It is abandoned by the caller, not
cancelled: it keeps running and its records may land late. Said plainly in
all three.

The hook parity test interpolated SCRUB_SCRIPT into a bash program;
JSON quoting does not stop backtick or ${...} expansion. The program is
now static and the path arrives as a positional argument.
@flyingrobots
flyingrobots merged commit def5d96 into main Aug 4, 2026
2 checks passed
@flyingrobots
flyingrobots deleted the split/capture-and-test-isolation branch August 4, 2026 10:52
flyingrobots pushed a commit that referenced this pull request Aug 4, 2026
#31 was split out of this branch, so main now carries reviewed versions of
the files this branch still held in their pre-review form. Every code and
doc conflict resolves to main:

- src/capture-followthrough.js — main clamps digit-only over-range budgets
  and measures the deadline with performance.now() plus a high-water mark;
  this branch still had the Number.isSafeInteger gate and a Date.now()
  deadline that a backward clock jump could extend
- test/ports/hook-git-env-parity.test.js — main passes the script path as a
  positional argument; this branch still interpolated it into the bash
  program, where JSON quoting does not stop backtick or ${...} expansion
- test/acceptance/repair-v17-mind.test.js — main only skips for a genuinely
  absent object, so a malformed manifest fails instead of reporting as a
  missing refs/cas/* object
- src/mcp/service.js, TECHNICAL-TEARDOWN.md — main says the followthrough is
  abandoned and may land late, not skipped

Nothing unique to this branch was lost in those files; the only content on
this side was the superseded original.

CHANGELOG.md is a real merge: main's corrected foundation entries, then the
20 entries unique to this branch covering the install tooling, the Codex
TOML hardening, the git-cas fixture and the README rewrite. The 10 entries
this branch carried for foundation work were dropped in favour of main's
wording, which is more accurate about deferral.
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.

1 participant