Skip to content

feat(macos): explain the Local Network grant for .local URLs - #315

Merged
roodboi merged 11 commits into
nextfrom
wf/local-network-hint-b11
Oct 12, 2026
Merged

roodboi merged 11 commits into
nextfrom
wf/local-network-hint-b11

Conversation

@roodboi

@roodboi roodboi commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What changes

On macOS, Hack now prints one fixed hint line on stderr when a command opens or prints a browser URL or dev host ending in .local:

Hint: on macOS, browsers other than Safari need a one-time Local Network permission to open .local names (System Settings > Privacy & Security > Local Network). See docs/guides/macos-browser-network.md.

Where it appears:

  • hack open (Compose and native-authored paths, with and without --json)
  • hack branch open
  • hack status / hack projects (human output, from the dev host column)
  • hack doctor browser check (the --browser-url origin, or the default domain when no origin is given). Native doctor shows it only for a given --browser-url.

Rules (in src/lib/local-network-hint.ts):

  • Shown only on darwin, only for hostnames ending in .local (case-insensitive, trailing root dot ignored).
  • Shown at most once per command (one process-wide emitter).
  • Always written to stderr. It never appears in a --json payload. Doctor JSON has no structured hints array, so nothing is added to the JSON data.
  • The text is a fixed constant with no interpolated host names.
  • Hack does not read, request or change the permission.

The product decision keeps hack.local as the default dev-host root. This change explains the one-time grant that browsers other than Safari need.

Docs

  • docs/guides/macos-browser-network.md: new section explaining the name-based rule (Apple TN3179: .local is the mDNS namespace, so the grant applies even when Hack resolves to loopback), the Safari exemption, the new hint, and alternatives:
    • the OAuth alias (<project>.hack.gy, hack open --prefer alias)
    • a custom suffix under hack.gy: hack global dns activate --domain dev.hack.gy for native, or default_domain plus hack global install for the Docker stack
  • Linked from docs/guides/init-project.md (default domain notes and the .local compatibility paragraph) and from the native DNS section of docs/guides/native-candidate.md.

Canonical agent guidance does not say that .local works in every browser, so it is unchanged. Plugins and render fingerprints are not regenerated. The CLI surface is unchanged, so docs/reference/cli.md is not regenerated.

Validation

  • tests/local-network-hint.test.ts (new):
    • The hint appears for .local URLs and hosts on darwin.
    • It does not appear for .hack, .hack.gy, .hack.local.gy, localhost, IPs or unparsable input.
    • It is never shown on linux, win32 or freebsd.
    • It is shown once per emitter.
    • The text is closed.
    • A real CLI open --json run uses an isolated HOME and state, no daemon and no Docker. It checks that the stdout JSON is exactly {url} with no hint, and that the hint appears on stderr exactly once on darwin (zero times on other platforms). A non-.local host produces no hint.
  • tests/integration-ownership.test.ts and tests/agent-plugin-generation.test.ts pass.
  • Related suites pass: tests/doctor-*.test.ts, tests/native-ca-doctor-cli.test.ts, tests/project-name-command.test.ts, tests/open-host.test.ts, tests/cli-reference-docs.test.ts, and the projects and branch tests.
  • bun run --cwd packages/cli typecheck: 0 errors.
  • bun run --cwd packages/cli check: 0 errors. The 34 existing warnings are unchanged, and none are in the changed files.

The tests do not touch /etc/resolver, dnsmasq, the keychain, Docker, the real HOME, sudo or osascript.

Remaining gaps

  • hack up prints no URL summary today, and hack ps prints no URLs (Compose or native), so neither shows the hint. This PR does not add a URL summary to up.
  • hack status / hack projects show the hint only in human output. Their --json daemon payload is passed through unchanged, so no hint is added there.
  • I have not observed the hint text in a real browser session. Chrome and Firefox behavior after the grant is taken from the product decision and TN3179, not from a test.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

roodboi and others added 10 commits October 12, 2026 13:52
…mands

A warm, image-only, unrouted `up` or `restart` with no hooks now runs
four directory-xattr storage proofs instead of six. Each proof is three
fresh helper containers.

- The warm enrollment tail (no new volume) reuses the effect-entry proof
  of the same generation under the same lease. It uses the existing
  adjacentProof record, which needs a byte-identical material binding,
  so any receipt write in between forces a full proof.
- Finalization skips the second proof pass when there is no route,
  after hook, or new or saved file material. In that case the
  completion step only reads.

Proof reuse stays within one command and one lease. Nothing is
persisted, so every command still opens with a full proof.

Each performed verification proof now emits a passive `storage.proof`
phase-trace span. The hosted native-config-compose scenario uses it to
assert four proofs on each of two consecutive warm ups.

Refs: HACK-1123

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sed file

The HACK-1211 experiment produced two iterations per Bun version: one
focused pass and one full `bun test` that started on a 600 s estimate and
overran the 30-minute budget, because the full suite takes 30-35 minutes
on hosted macOS.

- Never start an iteration whose estimate exceeds the remaining budget.
  The estimate is a conservative prior (focused 120 s, full 2400 s) until a
  run completes, then the longest completed run times 1.2. A command that
  does not fit is recorded once in summary.json `skippedForBudget` and
  leaves the rotation. Deadline stops remain a safety net (`overrun`).
- Add `--mode=focused|full|alternate` (default focused: only
  tests/native-https-owner.test.ts) and `--role=baseline|candidate`.
- The workflow gains a validated `mode` dispatch input; the label-triggered
  matched pair uses focused with baseline/candidate roles. Full mode needs a
  budget of at least 40 minutes. The job timeout stays budget + 15.
- summary.json gains per-command counts (iterations, passes, crashes, other
  failures, overruns, skipped-for-budget) and a one-line verdict such as
  "baseline 0/97 focused crashes".
- The shared classifier also treats exit 134 (SIGABRT) and 138 (SIGBUS) as
  crash exits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Native `up` emitted a `--ready` goal for every compose service, including
services in profiles that were not selected. The runtime does not schedule
inactive services and refuses readiness for them ("Readiness includes
inactive or unknown services."), so any project with an unused profile
failed with graph_readiness.

Build readiness goals only for services the reviewed plan marks active,
and ignore dependency edges declared by inactive services so they cannot
raise an active service's goal. The runtime refusal stays as a guard; a
contract test now proves it still rejects inactive and unknown services.

Bridge pool capacity keeps counting inactive routed services: it is sized
before the profile-aware review and must remain an upper bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On macOS, print one closed hint line on stderr when a command opens or
prints a browser URL or dev host ending in .local (hack open, hack branch
open, hack status/projects, and Doctor's browser check). Browsers other
than Safari need the macOS Local Network permission once for .local names
(Apple TN3179), even when Hack's resolver answers with loopback.

The hint is shown at most once per command, never on other platforms,
never for non-.local hosts, and never inside --json payloads. Hack does
not read or change the permission.

Document the name-based rule, the Safari exemption and the alternatives
(OAuth alias, or a custom suffix under hack.gy) in the macOS browser
guide, and link it from the init and native domain docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only `hack open --json` exercised the hint, so dropping it from the
human open path, branch open, projects/status or either doctor path left
tests green.

Run `hack open`, `hack branch open`, `hack projects` and `hack status`
from source with process.platform preloaded as darwin, stub `open` and
`docker` on a hermetic PATH, and an isolated HOME/state, asserting one
hint for a .local dev host and none for a non-.local host.

Doctor probes Docker, resolvers, the keychain and /usr/bin/curl, so its
compose and native tails now share `reportDoctorResults` with an
injectable hint, and each path's targets come from exported builders
that are tested directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a source CLI regression for a first up with new file material and no
saved file material: finalization keeps its completion step, the second
finalize pass, and the post-completion file-mount readiness check. The
test fails when the retirement condition keeps only the saved-files arm.

Native-compose CLI harnesses now give the child a private HOME under the
fixture root instead of the contributor's HOME.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicted files: none. The plain merge applied cleanly; no generated
files needed hand resolution, and the generation and reference tests
pass on the merged tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicted files: none. The plain merge resolved automatically. This
PR's change set (native readiness goals, the bridge capacity comment and
the project_contract.rs guard test) does not overlap witness reuse or
merge train 2. Regenerated plugins and docs/reference/cli.md were
byte-equal to the merged tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-hint-b11

Conflicted files: none. The merge applied cleanly; src/commands/doctor.ts
and src/commands/project.ts auto-merged with both sides' changes kept.

Regenerated surfaces (generate:agent-plugins, docs:cli-reference) produced
no further changes. src/agents/integration-revision.ts,
src/agents/integration-ownership.ts and the checked-in renders match the
predecessor branch, and their tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi
roodboi changed the base branch from next to wf/native-inactive-readiness-b10 October 12, 2026 20:52
@roodboi
roodboi marked this pull request as ready for review October 12, 2026 21:10
Base automatically changed from wf/native-inactive-readiness-b10 to next October 12, 2026 21:53
Re-parents onto next after the previous stacked PR squashed in; next's tree equals 1e6b892, which this branch already contains, so the branch tree is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi
roodboi merged commit 8c6390d into next Oct 12, 2026
31 checks passed
@roodboi
roodboi deleted the wf/local-network-hint-b11 branch October 12, 2026 21:53
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