Repository navigation
Conversation
setProjectEnvValue now keeps an entry's existing ciphertext when it already decrypts to the requested value under the current key, so repeating `hack env set --secret --value-stdin` with the same value leaves the tracked env file byte-identical and git status clean. A different value, a plain entry becoming secret, or an entry that does not open under the current key is re-encrypted with a fresh IV. Env layer writes now commit by rename from an exclusive, fsynced sibling, so a write interrupted before the commit leaves the previous layer intact and resolvable. Existing permission bits and symlink targets are preserved. HACK-P08 moves from deferred to partial: per-key no-op detection and atomic layer writes exist; a multi-key hydration import remains missing. Refs HACK-1152 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vior A `--local` env write killed before its commit rename left a `.hack.env*.<uuid>.tmp` staging file that git did not ignore, so a later `git add -A` could commit machine-local values, and a tracked-layer orphan left the tree dirty. The managed `.hack/.gitignore` now ignores `.hack.env*.tmp`, and `hack doctor` flags a tracked staging file. Tests now cover symlink preservation, staging cleanup after a failed rename, and clean git status after a killed tracked or local write. The argv-env-value recipe skips flags, so the P08 card can name `hack env set <key> --local --value-stdin --secret`. Docs describe the first-repeat normalization of hand-edited files and the rename edge cases. Refs HACK-1152 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs HACK-1152 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add `hack env hydrate`, which reads KEY=value entries (or one JSON object with --format json) from piped stdin or --file and writes them to the machine-local env layer in one rename-committed write. - Only the effective local layer is written; a git-tracked one is refused. - An entry that already holds its value is kept (secret ciphertext and scalar YAML type), and a no-op import does not rewrite the file. A changed value gets new ciphertext with a fresh IV. - Local entries outside the input, other scopes and removal markers for other keys are kept. - A secret in the layer that does not open under the reader key, a key-file mint that would shadow HACK_ENV_SECRET_KEY, a held or stale hydration lock, and a layer changed by another writer (compare-and-swap recheck before the rename) all refuse without changing the layer. - Input is bounded (1 MiB), strict and all-or-nothing; refusals are closed and value-free and name at most a line number. - Unexpected positional arguments are now reported by count, not echoed, so `hack env hydrate KEY=value` cannot print the value. Refs HACK-1158. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With stable ciphertext, setting an unchanged secret keeps the stored ciphertext, so the shared cipher fixture returned identical values and the ciphertext-only change tests no longer produced a change. Encrypt each fixture value in its own store so every call is a fresh encryption. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gitleaks flagged the literal HACK_ENV_SECRET_KEY fixture as a generic API key. Build it at runtime like the repo's other key-shaped fixtures and record the fingerprint of the branch commit that still holds the literal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Refuse hydration as tracking_unknown when the project is inside a git checkout but git cannot report whether it tracks the local layer (exit 128 from a safe.directory refusal or damaged index, or git missing). Outside any checkout, hydration still proceeds. - Refuse a plain import (no --secret) of a key the target scope stores as a secret (secret_downgrade), instead of silently replacing ciphertext with plaintext. - Refuse repeated top-level keys in --format json input (duplicate_key), matching the env format, instead of keeping the last value. - Pin the unreadable_layer refusal (invalid YAML, wrong shape, wrong version) and the key-file-before-HACK_ENV_SECRET_KEY reader priority with regression tests; share one runtime-built synthetic key helper. - docs/env.md lists every hydration refusal. Refs: HACK-1158 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rename-committed env write treated any read failure as an absent file, so `hack env set` on a layer it could not read (mode 000, or root-owned 0600 from an earlier `sudo hack`) built a layer holding only the new key and renamed it over the old one, dropping every other value. Rename also skips the write-permission check an in-place write got, so a read-only or foreign layer was silently replaced with a new owner. writeTextFileAtomicIfChanged now classifies the target before staging: only a missing path or a dangling symlink is absent. An existing target that cannot be read, is not a regular file, is not writable, or whose owner and group the staging file cannot take is refused with a closed, value-free AtomicWriteRefusedError, and the file is left as it was. A rewrite keeps the file's owner and group as well as its mode. Document the refusals and that rename detaches hard links, and list the staging pathspec in the doctor check docstring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve src/lib/fs.ts: keep F7's target inspection, which refuses an unreadable, non-regular, unwritable or unownable target instead of reading it as absent, and apply F8's expectedExisting compare-and-swap to the inspected text. The pre-rename check also uses the inspection, so a target that became unreadable is refused rather than matching an expected absence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve plugins/hack/skills/hack-cli/references/patterns.md by regenerating it (bun run generate:agent-plugins) from the merged src/agents/pattern-catalogue.ts, carrying this branch's pattern wording and the changes merged in train b18. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The F7 branch now includes next, which switched one private-file write in project-env-config.ts to publishPrivateFileExclusive and dropped the createPrivateFileExclusive import. This branch's hydration lock still creates its lock file exclusively, so import both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve generated plugin conflicts (plugins/hack/skills/hack-cli/references/patterns.md ) by regenerating them with bun run generate:agent-plugins from the merged canonical source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve generated plugin conflicts (plugins/hack/skills/hack-cli/references/patterns.md ) by regenerating them with bun run generate:agent-plugins from the merged canonical source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner_not_preserved refusal in writeTextFileAtomicIfChanged had no test, because a non-root user cannot create a file owned by someone else. A disposable child now reports the target as owned by another uid, so the staging file's fchown really fails with EPERM. The test expects the closed refusal, the old bytes and inode kept, and no staging file left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`hack run --profile X` on the native runtime refused every --env/--profile, even when X was exactly what `hack up --profile X` started (HACK-1276). Native run now compares a given --env or --profile with the selection the saved run mapping recorded at startup: profiles as a normalized set, env by its resolved name. An equal selection runs the one-off; a different one, or a legacy mapping without recorded selectors, refuses with a one-line E_USAGE before any runtime request. Omitted options still inherit the running selection, and run never restarts the project. The P02 README and the native candidate guide document the native flow. Refs HACK-1276 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
argv was parsed once against the union of every command's options keyed by long name, so the last declaration of a short flag won everywhere: `-p` became `ssh --port` for every command that declares `--path -p`, and run.ts refused `hack up -p <dir>` with `Option(s) not valid for "up": --port`. Parse in two passes instead. The first resolves the command path (and the child-command boundary) reading each option with the options of the command resolved so far; the second parses with that command's options plus the global and built-in ones. Options only other commands declare are still parsed by long name so they keep their "not valid for <command>" refusal; their short flags never leak. The per-scope guard test replaces the cross-tree type guard. Refs HACK-1293 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…losed refusals Native image-project `up` and `restart` now refuse an external Compose network, a reserved ownership label (com.docker.compose.*, io.hack-local.*, io.hack.native-config.*), a build outside the import preview's basic form, or a non-arm64 service platform before branch, env, lifecycle, storage or runtime work. Each refusal is one value-free E_NATIVE_PROJECT_UNSUPPORTED line that names the feature and the HACK_RUNTIME_BACKEND=compose selection. `hack doctor` reports the same features in a `compose backend features` check, and the native candidate guide lists which Compose features keep a project on the Compose backend. Refs HACK-1291 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se-feature-refusals-b21 # Conflicts: # src/commands/doctor.ts
…ation The HACK-1291 Compose-backend check read only the original Compose file, so an external network that a valid HACK_NATIVE_ADAPTATION selection isolates (the documented `isolatedNetworks` path) was refused before native start applied the adaptation. Native up, restart, restart --recover and native doctor now apply the selection's isolatedNetworks first. The selection is read only when the file has an external network; an invalid selection refuses with the same closed E_CONFIG_INVALID line native start gives, before any runtime request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… CLI case Under heavy host load the stub runtime's reply can time out or arrive unreadable. Each of those lines is only reachable after the Compose-backend check passed, so the adaptation case accepts all three. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…8-hydrate-b16 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#340 is now squash-merged into next (9a80dbd), so next and the F7 head (e99f68c) hold the same F7 text. Conflicts were resolved against that head as the effective base, keeping F8's changes and next's other work: - docs/env.md, src/agents/pattern-catalogue.ts, src/lib/project-env-config.ts: F8's text. Next's side was F7's text, which F8 had already extended (hydrate pointer, P08 card, CAS expectedExisting option). - src/cli/command.ts: keep #359's single-valued passthrough refusal exactly; other extra positionals are still reported by count only. - plugins/.../patterns.md: next's version, then regenerated (output equals F8's render). - src/lib/fs.ts merged without a conflict marker but duplicated writeTextFileAtomicIfChanged, AtomicWriteRefusedError and inspectAtomicWriteTarget; restored F8's single CAS-aware copy, which next's F7 version is a subset of. 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 b25: origin/next 2f43421 + #351 (HACK-1152 F8) + #365 (HACK-1291) + #368 (HACK-1276) + #370 (HACK-1293). Tree b42d11f8. Closed after the batch squashes.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.