Isolate spawned Stop hooks through TMPDIR/TMP/TEMP, open transcript fixtures exclusively with private modes, and derive intentional stale-race fixture identity from its opened descriptor.
Preserve main OpenCode lease and activation gates and native Claude namespace receipts. Detect same-size in-place edits using bigint modification and change times during hashing and before deletion; verify both races and actual public CLI reinstall lifecycles.
Port the evidence floor from PR #3480 onto the canonical generation-bound index; preserve policy-v5 anchors and the source PR attribution. Harden PR #3453 receipt metadata and pointer byte bounds.
Co-authored-by: montjeffrey <montjeffrey@gmail.com>
Isolate deadline fixtures from inherited dry-run/disabled hook flags. Remove an unapproved emoji from the new routing regression to satisfy Unicode validation.
Each writer performs 30 write rounds to widen the race window. Drop the vacuous exitCode liveness assert and the swallowing mid-flight read; instead every wait-loop poll reads and validates the state, so torn reads fail loudly.
Assert dry-run install writes nothing under projectRoot too (cursor target writes there, not home). Rewrite the concurrent writeInstallState test with genuinely overlapping writers via async spawn plus done-file rendezvous, including a mid-flight read; sanitize rendezvous filenames for Windows.
Honor ECC_DRY_RUN=1 in install and repair arg parsing (previously only --dry-run flag worked, so ecc --dry-run install/repair silently performed real writes). Make writeInstallState atomic via writeFileAtomic (O_EXCL staging, fsync, cleanup on failure, atomic rename preserving previous state). Add writeInstallState failure/concurrency/symlink regression tests and disposable-home CLI dry-run regression tests for both global and subcommand forms.
Resolve six conflicts:
- hooks/hooks.metadata.json: keep this branch's gateguard description and
fingerprint, add upstream's hookify-runtime entries.
- package.json: keep both gateguard-report.js and github-coordination.js.
- scripts/hooks/gateguard-fact-force.js, tests/hooks/gateguard-fact-force.test.js,
skills/gateguard/SKILL.md: upstream added a simpler session denial cap; this
branch already has the fuller one (cap threaded through
markCheckedAndCountDenial, malformed-value warning, miss and target notes),
so keep it. Upstream's taskset and su wrapper handling, fail-closed
recursion limit and soft-hyphen sanitising merged cleanly and their tests pass.
- manifests/context-packs/skill-triggers@1.json: take upstream's file and
recompute registryDigest for the merged registry.
Treat non-2xx activity responses as feed errors, preserve non-Markdown Copilot agent assets, and apply the reviewed reference escape parity correction with regenerated bindings. Allow the test runner to drain piped output on its existing success or failure exit status. Validate installed workflows and bounded real Node20/24 pipe regressions.
- A build interrupted while writing the index leaves a partial file under
its digest name before the pointer exists. The next build rejects those
bytes ("content changed under its digest") and cannot repair the index.
- A skill edited after the entries are read leaves an index that still
matches the stored generation, and the next build reports that
generation stale. This pins the current behavior: the index is bound to
the generation, not to live sources.
plugin-hook-bootstrap (shell mode) and observe-runner probed a bare
`bash.exe`. On Windows that resolves to the first match on PATH, and
%SystemRoot%\System32 normally sits ahead of Git's bin directory, so the
pick was System32\bash.exe: the WSL launcher. The existing guard (require
a clean probe exit) only catches it when no distro is installed. With one
installed, the probe passes inside Linux, then the hook script is handed
over by a Windows path (C:\...) or a Git Bash path (/c/...), neither of
which exists in WSL. The hook exits 127 instead of running, and
observe-runner's continuous-learning observation fails on every call.
Resolve bash candidates on Windows to absolute paths from PATH, skipping
bash.exe in System32, SysWOW64, Sysnative, and the WindowsApps alias
directory, so a native bash later on PATH (Git's) is used. With none
available, the existing "no bash binary found" skip applies. An explicit
BASH override still wins, and other platforms keep bare names.
The shared resolver lives in scripts/lib/windows-bash.js with a
platform-independent test. A Windows integration test puts System32 ahead
of Git's bash and requires the .sh hook to actually run. Upstream's
bootstrap fails that scenario with exit 127 ("/bin/bash: C:Users...hook.sh:
No such file or directory"); this one prints the hook output.
Carry forward the final scoped delta of PR #3119 at 60e82f95. Keep required dependency closure and select only modules reachable from successful requests; refresh the unchanged 293-skill registry binding.
Carry forward PR #3115ef475c79 and 780dfb78 onto current main. Preserve the newer hook-consent transforms during install and repair. Main already includes the source import fix from #3167.
On a Windows machine with Claude Code installed natively, Git for Windows,
and the ECC plugin installed, `node tests/run-all.js` on main fails 44 tests
in 7 files. Linux CI never sees them. Three of the four causes are test
isolation gaps, fixed here (43 tests):
- claude-plugin-setup, claude-scope-migration, setup (38): fixtures put a
fake claude.cmd first on PATH, but on Windows spawning a bare `claude`
resolves only .com/.exe, so a native ~/.local/bin/claude.exe ran instead
of the fake. Fixture PATHs now drop entries holding claude.exe/claude.com.
- ecc-universal-bin, eval-harness-package (4): tar received an absolute
C:\ archive path, which Git Bash's GNU tar reads as a host:path remote
spec ("Cannot connect to C: resolve failed"). Extract from inside the
destination with a relative, forward-slash archive name, which both GNU
tar and bsdtar accept.
- harness-audit (1): the cache-layout test overrode HOME only, while
findPluginInstall also reads USERPROFILE and os.homedir(), so the real
installed plugin won over the fixture. Override USERPROFILE too.
The two helpers live in tests/lib/helpers/windows-test-env.js with a
platform-independent test, so Linux CI holds both invariants too.
The 44th failure, plugin-hook-bootstrap's no-bash test, is a production
bug rather than a test leak: System32\bash.exe launches an installed WSL
distro, which passes the bash probe. It gets a separate fix.
Keep the operation lock while validating bigint file identity and reuse
only snapshots produced by successful trusted static SELECTs. Avoid
reloading and exporting unchanged databases on repeated public reads.
Retain conservative comparison for generic SQL, including RETURNING and
PRAGMA writes, and invalidate reuse after transactions, exports or errors.
Cover cache invalidation, cross-process visibility, Windows device IDs,
reentrant partial writes, lock contention and independent result objects.
Validate 188 focused tests per environment on WSL Node 22.22.3 and native
Windows Node 22.15.0/24.16.0, plus 38 related WSL tests. Paired warm-read
benchmarks show 7.6-18.1x point-query speedup on 160 KiB/1.28 MiB databases.
Handle Windows Node path stats that report dev=0 for the same file whose
open descriptor reports a volume serial. Keep bigint inode checks strict,
retain device comparisons whenever both are known, and leave POSIX strict.
Cover zero-device identity cases, high inode precision, replacement and
file-type safeguards. Verify 164 focused tests each on native Windows
Node 22.15.0 and 24.16.0, plus WSL Ubuntu Node 22.22.3.
Compare the completed snapshot after get/all queries so RETURNING and
prepared PRAGMA writes survive reloads and reopening. Export only after
the outer operation or transaction succeeds; unchanged read snapshots
still avoid disk writes. This adds one export/byte comparison per read
snapshot without relying on SQL parsing or private sql.js APIs.
Add 18 regression cases covering multi-row INSERT/UPDATE/DELETE RETURNING,
standalone and transactional get/all, no-row PRAGMA writes, and rollback.
The unfixed adapter fails 16 of these cases. Strengthen read-only checks.
Validation: 148 focused tests pass on WSL Node 22 and Windows Node 24;
the general state-store suite passes 20/20 on WSL and 19/20 on Windows,
where the existing file-symlink fixture is denied by Windows permissions.
Scoped adapter coverage is 90.13% lines, 82.73% branches, 100% functions.
ESLint, Unicode safety, and git diff --check pass.
LANGUAGE_RULES had no entry for Dart, and FRAMEWORK_RULES had no way to
detect Flutter, so any Flutter app (pubspec.yaml, lib/*.dart, android/,
ios/) was reported as primary: "unknown" by the SessionStart project-type
banner, despite ECC shipping a dart-flutter-patterns skill and a
dart-build-resolver agent for this exact stack.
Adds a 'dart' language rule (pubspec.yaml/pubspec.lock markers, .dart
extension) and a 'flutter' framework rule that checks pubspec.yaml for
the standard `flutter: / sdk: flutter` dependency shape, via a new
customCheck hook on FRAMEWORK_RULES entries. This distinguishes Flutter
apps from plain Dart packages, which don't depend on the Flutter SDK.
Closes#3469