v1.91.47.0 fix(careful,freeze): safety hooks cover PowerShell and NotebookEdit (#3077)

* fix(careful): check PowerShell tool commands and nested pwsh/cmd (#3067)

/careful and /guard matched only the Bash tool, so on Windows, where
Claude Code's PowerShell tool is the primary shell, the guard never ran.
check-careful.sh also had no PowerShell or cmd patterns.

- careful/guard register a PowerShell matcher beside Bash.
- hook-extract.sh gains gstack_hook_extract_tool: one parser call reads
  tool_name with the field, so dispatch adds no fork.
- check-careful.sh runs one PowerShell/cmd pattern table on PowerShell
  tool commands and on the tail of any Bash command that launches
  pwsh/powershell/cmd: Remove-Item and aliases with -Recurse/-Force
  prefixes, rd/rmdir/del/erase /s, Format-Volume, Clear-Disk,
  Clear-Content, [IO.Directory]/[IO.File]::Delete, and asks with an
  explanation for -EncodedCommand, iex, Start-Process of a shell and
  & $cmd. Command-position anchors keep Bash negative controls allowed.
- /careful documents the coverage as best-effort and points to Claude
  Code permission deny rules.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(freeze): NotebookEdit respects the freeze boundary (#3067)

NotebookEdit was in no freeze/guard/investigate matcher, and
check-freeze.sh read only file_path, so notebook edits ignored /freeze on
every platform. NotebookEdit carries tool_input.notebook_path.

- freeze, guard and investigate register a NotebookEdit matcher beside
  Edit and Write.
- check-freeze.sh reads file_path, then notebook_path, through the shared
  extractor; denials name the tool, the field, the boundary and
  /unfreeze. A helper without the combined reader denies as out of date.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: run hook-scripts on the Windows lane (#3067)

The careful/freeze hooks are Windows coverage, but test/hook-scripts.test.ts
was excluded from windows-free-tests by the raw /tmp/ pattern; its /tmp/
literals are payload strings that are never opened. Admit it by name and
gate the three cases Windows runners cannot host: a symlink (no Developer
Mode), a newline in a directory name, and a POSIX ':' PATH prefix.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore(evals): check-careful.sh is free-only for PR selection

No paid fixture copies or runs careful/bin/check-careful.sh (the
investigate boundary fixture copies hook-extract.sh and check-freeze.sh
only); test/hook-scripts.test.ts covers it. As an unknown dependency it
restored the full paid gate on every /careful hook change.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(careful): pin the #3067 Invoke-Sqlcmd DROP TABLE example on the PowerShell tool

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore(evals): record the check-careful.sh selection change as non-behavioral

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: /careful covers PowerShell (best-effort), /freeze covers NotebookEdit (#3067)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: release v1.91.37.0 (CHANGELOG, TODOS for deferred hook follow-ups)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: re-slot release to v1.91.39.0 (v1.91.37.0 and v1.91.38.0 claimed by #3075, #3076)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: re-slot release to v1.91.41.0 (merge-queue reservation)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(careful): hook payload reader survives Windows text-mode Python and backslash source paths

windows-free-tests (run 37739845723) showed every PowerShell-tool case
falling through: Python's text-mode stdout on Windows wrote tool_name as
"PowerShell\r", so dispatch never matched, and freeze denials carried
stray CRs. gstack_hook_extract_tool now reads and writes bytes.

hook-extract.sh also located gstack-state-root.sh with ${BASH_SOURCE%/*},
which strips nothing when the file is sourced through a C:\... path, so
gstack_hook_state_root vanished (state-root parity case on the same run).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): PR lane selects office-hours-auto-mode by its real test name

The PR paid lane filters audited files with -t built from
CASE_TEST_NAMES[id] ?? id. office-hours-auto-mode's Bun test is named
'AskUserQuestion surfaces when --disallowedTools AskUserQuestion is set',
so the filter matched 0 tests and the shard failed (#3077 eval-slices (6),
run 37741434771). Map the label, and check every audited PR-profile case
is addressable by name. Recorded as non-behavioral for HARNESS_VERSION.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: CHANGELOG notes for the Windows CRLF and PR-lane name fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: re-slot release to v1.91.47.0 (merge-queue assignment)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-10-08 08:18:26 +00:00
1 parent 92cfd07a79
commit c7edf46bcf
25 files changed
+763 -59

No files matched your search

+2 -2
View File
@@ -10,13 +10,13 @@
"test/helpers/llm-judge.ts"
],
"files": {
"scripts/lib/paid-cases.ts": {"blob":"9bacd52446192a7ecf3dfaae901ea443cf9c9ae2","since":4},
"scripts/lib/paid-cases.ts": {"blob":"c2c16290522e12df7bec88047737920636daad44","since":4,"nonBehavioral":"Merge with main (HARNESS_VERSION 4): FREE_ONLY_PR_FILES gains careful/bin/check-careful.sh and CASE_TEST_NAMES maps office-hours-auto-mode to its real Bun test name; selection only, no case verdict rule changes"},
"scripts/lib/paid-plan.ts": {"blob":"45721ce128a112666a0caed6c13c66637b74ca54","since":4},
"scripts/lib/paid-report.ts": {"blob":"3fda96a8b1f7f3d55797934847353f254cc4d054","since":4},
"scripts/lib/published-text.ts": {"blob":"3f2e4d616b5dd0204035710c66b64d49055c5714","since":4},
"scripts/lib/shard-engine.ts": {"blob":"3cd0b1aaa84018b00bf9ea0ce48093f3c8492f66","since":4},
"scripts/test-paid-shards.ts": {"blob":"8ddee5f11ed7856876135520dd8025ab989e2db9","since":4},
"scripts/test-pr-profile.ts": {"blob":"54363c3d3aad9743b9e79219da7ec8a0f4ab0832","since":4},
"scripts/test-pr-profile.ts": {"blob":"791d101b37e81bdc200e33f72abc3cc12eeaab0f","since":4,"nonBehavioral":"Merge with main (HARNESS_VERSION 4): FREE_ONLY_PR_FILES gains careful/bin/check-careful.sh and CASE_TEST_NAMES maps office-hours-auto-mode to its real Bun test name; selection only, no case verdict rule changes"},
"scripts/test-strict-output.ts": {"blob":"01185d20dc9761b04b9b2f751ec98664fe0eaf93","since":4},
"test/helpers/agent-sdk-runner.ts": {"blob":"210bba92d241a90df578b2c7fc943ac5755d0025","since":4},
"test/helpers/auq-substance-panel.ts": {"blob":"4881260ef793668e4963a062db6d06a5e1f7a40f","since":4},
+1
View File
@@ -76,6 +76,7 @@ export const CASE_TEST_NAMES: Record<string, string> = {
'plan-ceo-review-plan-mode': 'first terminal outcome is asked (Step 0 fires before any plan write)',
'plan-eng-review-artifact': 'an interactive review writes one QA test plan about the reviewed change',
'plan-eng-review-artifact-full': 'a fresh interactive review reaches Test review and writes one QA test plan',
'office-hours-auto-mode': 'AskUserQuestion surfaces when --disallowedTools AskUserQuestion is set',
};
export const CASE_KEY_SEPARATOR = '#';
+1
View File
@@ -240,6 +240,7 @@ export const KNOWN_WINDOWS_SAFE: Array<{ file: string; reason: string }> = [
{ file: 'test/generator-eexist.test.ts',
reason: 'E4: runs the generators through Bun argv with the Windows EEXIST emulation preload; no shebang execution' },
{ file: 'test/gstack-config-gbrain-refresh.test.ts', reason: 'E6: runs bin/gstack-config through explicit bash; its gbrain/python3 shims are found by bash PATH lookup, never launched by CreateProcess' },
{ file: 'test/hook-scripts.test.ts', reason: '#3067 PowerShell and NotebookEdit hook coverage: every hook runs through spawnSync("bash", [script]) with the payload on stdin, scratch state lives under os.tmpdir(); its /tmp/ literals are payload strings, never opened; symlink, newline-path and POSIX-PATH cases are skipIf win32' },
{ file: 'test/gstack-doctor.test.ts', reason: 'runs bin/gstack-doctor and setup --status through explicit bash argv in an os.tmpdir fixture; its bun/codex/claude/probe stubs are found by bash PATH lookup, and expected paths use bash spellings' },
{
file: 'test/qa-evidence.test.ts',
+1
View File
@@ -212,6 +212,7 @@ export const FREE_ONLY_PR_FILES = [
'tsconfig.test.json', // Read only by `tsc -p tsconfig.test.json` (typecheck:test); Bun's runtime reads tsconfig.json.
'.gitignore', // Changes which untracked files git reports, never tracked content a case reads.
'scripts/retired-command.ts', // One-release stubs for retired package scripts; no paid case imports it.
'careful/bin/check-careful.sh', // /careful PreToolUse hook: covered by test/hook-scripts.test.ts; no paid fixture copies or runs it.
// Reporting and launch tools: they read CI history or start a lane, never run inside a paid case.
'scripts/test-health-report.ts',
'scripts/bump-harness-version.ts',