Repository navigation
Conversation
…s unique HACK-1347: in the shared parallel test process, an unrelated test's pre-exec fork (process::clean_command) holds a copy of every open descriptor until it execs. Endpoint discovery then sees the fork as a second holder of the test's listener (or finds a listener the test already dropped), and the lsof release checks see disk handles the fixture had just written, so they correctly refuse before the result under test. Run every such fixture alone in a child test process with the shared run_isolated helper: - host_endpoint: exclusive and executable-pinned discovery, plus the two close/rebind fixtures that still shared the process; the module's own runner now delegates to run_isolated (120 s hang guard, not 5 s). - admission_pool: every release-check test that expects a pass or a non-active refusal (7 more), behind one isolated() helper that keeps serial scans. - short_home, host_filesystem, interrupted: recovery, migration and stop tests that need the lsof proof to pass. HACK-1348: fixtures named scratch directories from the PID and the wall clock and created them exclusively. macOS reports the clock in whole microseconds, so parallel tests can read the same value and the second create fails with "File exists (os error 17)". Add crate::next_test_scratch() and append it to every such name in the lib tests, with a regression test. No product code changes. Refs HACK-1347 Refs HACK-1348 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HACK-1347 audit, second part: more runtime-core tests drop a listener (or a publication that owns one) in the shared parallel test process and then require a release check to pass. An unrelated test's pre-exec fork (process::clean_command) holds a copy of every open descriptor until it execs, so the dropped listener still accepts and its flock stays held; the check correctly refuses and the test fails. Under in-process fork pressure with a 20 ms pre-exec hold the full all-features lib suite failed 3/3 runs on 9 to 13 such tests. - Make provider::test_executable available on every Unix target (the quiescent-recovery tests also run on Linux) and isolate, with run_isolated: dependency_slots socket recovery (2), quiescent_recovery dropped-listener tests (2), foreground::transport abandoned-publisher tests that expect retirement to pass (7), acknowledged_publisher::missing_lock (5, all use the dead-publisher fixture), native foreground abandoned publication (1) and the https_recovery full commands (2, their fixture drops the provider flock that recovery must then acquire). - Fixtures that only need a stale socket inode now bind a socket that never listens (new provider::test_socket::bound_unlistened, shared with the quiescent-recovery fixture) instead of dropping a listener: https_recovery and dependency_slots::fixture_prior_boot. Regression test included. - relay_exec framing test: shut the peer down instead of only dropping it, so the truncated frame always reaches end of stream. No product code changes. Refs HACK-1347 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
provider::publisher::tests::Fixture picked a port by binding 127.0.0.1:0,
reading the port and dropping the probe, then started stream-relay-host on
that explicit port. In the shared parallel test process an unrelated test's
pre-exec fork (process::clean_command) can still hold the probe listener, or
another test can take the port, and the publisher exits 73 without
readiness ("publisher readiness missing", 2 of 5 full-suite runs under fork
pressure with a 20 ms pre-exec hold).
Start the publisher with --publish 0 (from #382): it binds a kernel-chosen
port and reports it as "ready <port>", which the fixture now requires.
Refs HACK-1347
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s adopted generated-files warning The adoption selector stopped after 64 directories and then refused the command as an unsupported adopted command, so `hack ps`, `up`, `down`, `logs` and `open` from a deep directory with no project exited with E_NATIVE_PROJECT_UNSUPPORTED. The selector now walks to the nearest project root or the filesystem root, as ordinary discovery does. Returning "no adoption" at a bound instead is unsafe: ordinary discovery reads an active adoption as native input, so `up` from deep inside an adopted project would reach native startup. For an adopted project, doctor's generated files warning now says that --fix leaves the adoption's .hack/.gitignore unchanged instead of promising to ensure it. Refs HACK-1380 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…60 min limit The serial `test` job ran all ~570 bun test files in one Turbo task and took 37-42 min, so slow hosted runners were cancelled at GitHub's 60 min job limit, and Turbo's buffered output could not name the file that hung. CI now runs four `test shard i/4` jobs. scripts/test-shard-plan.ts splits the `bun run test` file set (root tests/ with bunfig preloads, plus packages/db) by the checked-in durations in scripts/test-shard-durations.json, longest first, with a default for new files. scripts/test-shard.ts runs each file in its own `bun test` process from its suite directory, prints a start and a result line per file, stops a file after 12 min and the shard at 30 min or on SIGINT/SIGTERM, and then names the running file, the unstarted files and the slowest files. Every shard first verifies that the four shards cover the files git lists exactly once. The required `test` check is now a fan-in that passes only when every shard succeeded; the prerelease gate also requires each shard by name. Refs HACK-1381 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rain late The foreground graph owner drained its child's stdout and stderr for only 250 ms after exit. Under heavy oversubscription the pipe readers could be scheduled after that window, so the closed native error code (or readiness line) was dropped and a generic startup failure was reported. Replace the window with the named, documented NATIVE_EXIT_DRAIN_BOUND_MS (5 s), still measured from the observed exit. EOF still ends a normal drain at once; the bound only ends a drain that a descendant holding the pipes keeps open. Tests: a writer that emits the code only after the stand-in is reaped is kept (fails with the 250 ms window), and a held descendant still settles within the bound while it is alive (fails with a widened drain). Refs HACK-1350 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 4dfbb6a)
…it bound Bounded one-shot requests (status, logs, exec, one-off, read and recovery) stopped reading the child's stdout and stderr 250 ms after its exit. Under heavy oversubscription the closed native error code could arrive after that window, so it was dropped and a generic request failure was reported. Share NATIVE_EXIT_DRAIN_BOUND_MS (5 s, measured from the observed exit) from the runtime client with the foreground graph owner, which #396 moved off the same 250 ms window. EOF still ends a normal drain at once; the bound only ends a drain that a descendant holding a pipe keeps open. Both boundaries take an optional exitDrainBoundMs test seam that defaults to the constant. Tests: a stderr writer that emits the closed code only after the stand-in is reaped is kept for status, read, logs and exec (fails with a 250 ms window); the held-descendant cases now settle within the named bound plus a margin (fail with an unbounded drain). The graph owner's late-writer case injects a wide named bound and uses named handshake budgets so host load cannot flake it; its held-descendant cases keep the production bound. Refs HACK-1387 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… every selector in Loki hints Follow-ups to #417 for Loki-backed `hack logs` (HACK-1379). - The global logging compose gives Alloy a named `alloy-data` volume at `/var/lib/alloy/data` and repeats `--storage.path` there, so read positions survive a recreated container instead of re-reading every retained log. - `lokiTail` removes its SIGINT listener when the socket closes, so repeated follows in one process no longer pile up listeners. - Empty-snapshot and empty-follow hints carry `--tail`, `--profile`, `--pretty`, `--json` and a single service, and name `--services`, `--query`, `--since` and `--until` when the compose backend cannot apply them. - The empty-follow notice timer is injectable; tests drive it by hand and prove the tail keeps following after the notice. - The template comment and docs/architecture.md state Alloy v1.12.0's exact reopen condition (`State.Running || finished.Unix() >= target.Last()`). Refs HACK-1379 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s and exec refusals The logs and exec "inherited pipe refuses output" cases spawned a keeper that slept 800 ms: longer than the former 250 ms window, but inside the shared NATIVE_EXIT_DRAIN_BOUND_MS, so its EOF now settled the output and the requests resolved instead of refusing. The keeper now holds the inherited streams until the test releases it after the refusal (self-capped at 60 s), so the drain can only end at the bound, and the case budgets derive from that bound. Refs HACK-1387 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up to #377 (HACK-1333) for HACK-1389. Four gaps in the application benchmark protocol checker: - An out-of-range summary number is now reported alongside summary_revision_mismatch and summary_workload_unknown, as the page says, instead of being hidden by their early return. - A repeated object key is refused as protocol_key_duplicate or summary_key_duplicate. json.loads kept only the last value, so a NaN under a shadowed key was never scanned and the protocol validated. - An integer literal longer than MAX_JSON_NUMBER's 16 digits parses as just out of range, so a 5000-digit literal is protocol_number_out_of_range on Python 3.9 and 3.11+ alike (3.11+ refused the conversion, which surfaced as protocol_unreadable). Exact identity checks treat an unserializable value as different instead of raising. - A test pins the page's ±(2⁵³ − 1) bound and the code names to MAX_JSON_NUMBER and DIAGNOSTICS. Refs HACK-1389 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An env layer lock that Hack can never reclaim (a malformed or untrusted receipt, a reused PID, a dead owner behind a leftover `<lock>.recovery` guard) made every set/add/unset/hydrate refuse with "Retry when it finishes." forever. The timeout now names the lock path and the recovery step, and keeps the registry's "recovery guard is busy" detail with both paths. Closed, one line, value-free. - The unignored-key refusal no longer says Hack "could not add" the entry when the root .gitignore already lists the key and a later rule re-includes it; it names that rule as the cause. - A later `git init` is out of scope for env writes (Hack runs no code then). `hack doctor`'s env key check is the backstop: it flags the unignored key, and the next --secret write repairs the entry. Documented in docs/env.md and covered end to end. - Tests: paused-unset compare-and-swap conflict, unreclaimable lock receipts, recovery guard (set and hydrate), re-include message, and the git-init doctor scenario. Each was proven by mutation. Refs HACK-1386 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oll costs Alloy v1.12.0 reopens an exited container's log when `finished.Unix() >= target.Last()`. A reopen reads from the position second, which Docker includes, so it reads the last line again and moves `Last()` past the finish time. Only a container that never logged a line is reopened every 5 seconds; one that logged, or whose lines the positions file covers, is reopened once and then stays closed. Every discovered container still costs one inspect call per poll. Refs HACK-1379 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The late-writer cases waited only for the stand-in's pid file to exist and
then read it. writeFileSync creates the file before it writes the digits, so
under load the read could return "": Number("") is 0, kill(0, 0) always
succeeds, and the reaped-stand-in wait expired. Both files now read pid
handshakes through waitForPid, which waits for a safe-integer pid above 1,
and the late-writer stand-ins publish their pid as a torn handshake (empty
file, 50 ms gap, then the pid) so an existence-only reader fails every run.
The keeper pid reads use the same helper.
Refs HACK-1387
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The exec env-change test asserted an empty stdout but relied on an earlier test file leaking HACK_LOGGER=console into the shared bun process. Run alone (as each test shard now runs files), the logger fell back to clack on hosts without gum, which writes the CoreDNS/Caddy warnings to stdout, so the assertion failed. Set HACK_LOGGER=console in beforeAll and restore it in afterAll so the file passes in any order. Refs HACK-1381 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ted-selector-depth
…passes The spawn-env isolation guard counts any whole-object use of process.env as a copy of the runner's environment, so withPrivateEnvironment's Object.assign(process.env, overrides) failed it in hosted CI. Set each override by key and restore it with the shared restoreEnv helper; the values were already private. Refs HACK-1386 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elector walks every ancestor After merging next (#432), the adoption selector returns null for a deep start directory with no adoption, so doctor's E_NATIVE_PROJECT_UNSUPPORTED catch was dead code that could hide a future refusal. Remove it, update the doctor helper's comments, the HACK-1374 deep-start test (the selector now resolves null) and the doctor paragraph of the adoption reference, which still described the 64-directory bound. Refs HACK-1380 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…itignore
ensureHackGitInfoExclude now applies the same probe-error policy as
ensureHackDirGitignore: an ENOENT from the write probe counts as writable
(the rename recreates the file); any other probe error is rethrown and
the best-effort wrapper skips the update with a debug log.
Also restores the exact toEqual({ follow: true }) assertion for doctor
and up, stops asserting Bun's null resolve value of fs.promises.access,
and corrects docs/cli.md: under clack the refusal is two lines (the
guide line and the marker line), not one.
Refs HACK-1412
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the src/commands/doctor.ts docblock conflict with #448 to the PR side: keep the extended checkProjectEnvKey docblock and its export. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#405 (HACK-1392) landed its own fix for the Loki follow SIGINT leak: the handler is installed just before the tail awaits close and removed in a finally. Keep that version and drop this branch's close-handler removal and its two SIGINT tests in favour of next's; keep the injectable notice scheduler, the Alloy positions volume and the selector-carrying hints. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efusals Follow-ups to #452 (HACK-1390) on the target inspector. - LegacyComposeAdoptionSelectionError's `permission` and `input-access` kinds now keep the same non-enumerable class/code diagnostic as ProjectInputAccessError (read with legacyComposeAdoptionSelectionDiagnostic). The helpers move to src/lib/value-free-diagnostic.ts so both errors share them. - Pin that inputPresent() classifies through projectInputAccessFault by faulting the real presence check with EPERM, EACCES and EIO. - Paired CLI/MCP fixtures now cover .hack and .hack/.internal at modes 000 and 0644, matching the proof-matrix row. - The input_permission_denied message assertions run unconditionally, also as root. Refs HACK-1415 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ched task surfaces Close the task catalog follow-ups from #430 and #409. HACK-1411: older Git's `rebase--helper --add-exec-commands cmd` is now refused through a `rebase`-scoped inline flag, so `git config --add` still passes. Pin the mapping of a dashed builtin's trailing arguments (`git-config alias.x bisect--helper` stays refused), pin the lowercased porcelain (`git Rebase--Helper --add-exec-commands cmd`), document and test the new fail-closed collateral (`git push -u origin clone--helper`, `git add bisect--helper`, `git config --add alias.r rebase`), and label the helper case HACK-1373. HACK-1383: decision 7 lists both remedies the detached_checkout refusal offers (`--branch`/MCP `branch`, or `worktree.auto_branch=false`), and a regression covers CLI `task inspect` and both MCP task tools in a detached linked worktree, each surface with its own assertion. Refs HACK-1411, HACK-1383 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…yes, and test every prompt guard Follow-ups to #446 (HACK-1385): - Add one bounded non-TTY spawn test per canPrompt() guard (mcp target selection, session picker and attached-workspace choice, update install confirmation, secrets name/value prompts, agent client picker), each proven by mutation. - Guard the clack prompts in `hack ssh` (connection method, host, follow-up choice, Tailscale start) and `hack node pair walkthrough` (input prompts now gate on canPrompt() instead of stdout alone; the execute-now confirm leaves the session pending without a TTY). - `hack secrets delete <name> --yes` confirms the deletion explicitly; without it a TTY-less run still refuses closed with E_INTERACTIVE_REQUIRED, and the hint now names --yes. - tests/global-command.macos.test.ts keeps a TTY-less case for the decline and accept-default policies and asserts the prompt is never entered. Refs HACK-1410 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r a pinned literal override An interrupted preparation stop (pending operation, no publication) and an interrupted retained command of an active adoption both reported the pending switch step 'hack config adopt --recover --rollback', which each of those states refuses. Doctor and the --fix note now name 'hack config adopt --recover --stop' and 'hack down --recover' there. A busy refusal over a pending switch now names the step that state accepts instead of only the generic interrupted-lock recovery, which completes the switch and refuses a literal override. Refs HACK-1414 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration gate for merge batch b52: origin/next + #399 (HACK-1347, HACK-1348) + #441 (HACK-1380) + #447 (HACK-1387) + #449 (HACK-1379) + #453 (HACK-1386) + #451 (HACK-1389) + #466 (HACK-1410) + #463 (HACK-1411, HACK-1383) + #460 (HACK-1412) + #467 (HACK-1414) + #462 (HACK-1415) + #442 (HACK-1381, CI sharding last). Tree aaf21185. Closed after the batch squashes.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.