Ship Nod in the app bundle and make it launchable where its ramp is on - #600
Merged
Merged
Conversation
- `make build-nod` packages NodRuntime; `release-dmg` copies it to Contents/Helpers/nod and signs it with the runtime's JIT entitlements. - `.nod` is spiked wherever this process could launch it: the ramp flag is set and a runtime is present. The app, daemon and CLI install that check at startup (`NodRuntimeLocator.installAvailability`); the test host does not, so hosting assertions never depend on the Mac's own install. - The app publishes the ramp flag at launch from the cached ramp, so a fresh beta install need not wait on the network fetch. - A loop's lineage brief rides `--inherit` on a fresh launch only, and a composite child launches `--unattended`. - The chat pane reads its state directory from `NodRuntimeLocator`; the app's duplicate helper is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: scgopi <scgopireddy@gmail.com>
scgopi
commented
Oct 2, 2026
scgopi
left a comment
Owner
Author
There was a problem hiding this comment.
Independent review — ✅ no blockers (would approve; GitHub won't let the author's account approve its own PR, so this is posted as a comment)
Reviewed at 9539b55f against origin/main 9ff1442c. Nothing here blocks the merge. The notes below are follow-ups.
What I checked
| Area | Verdict | Evidence |
|---|---|---|
make build-nod |
✅ | package.sh <out> bun-darwin-arm64 writes graphcode-nod, copilot-runtime, runtime.node and claude flat into .build/nod, which matches the three names the signing loop expects. Arm64-only is consistent with release-dmg |
Bundling into Contents/Helpers/nod |
✅ | ditto keeps modes and mtimes. DaemonBootstrap.bundledNodDirectory resolves Contents/Helpers/nod from Resources/bin, and installNodRuntime copies the whole directory and clears quarantine |
| Signing (Developer ID branch) | ✅ | The three Nod binaries are signed with hardened runtime, a timestamp and the JIT entitlements before the outer app is sealed. In the ad-hoc branch, --deep re-signs them without hardened runtime, so JIT isn't needed there |
| Bundled Claude binary | ✅ (not run) | claude-agent-sdk-darwin-arm64/claude is already Developer ID Application: Anthropic PBC (Q6L2SF6YDW) with the runtime flag and a secure timestamp, so leaving Anthropic's signature in place should pass notarization. No signed or notarized release-dmg run is in the evidence, so this part is still unproven |
isSpiked hook |
✅ | Installed in all three shipped processes, in the right order: app init (after SupportDirectory.prepare, before the lazy static store), graphcoded (straight after prepare) and the CLI (before parse). The check runs lazily on every call, so the daemon picks up a ramp flag or runtime that appears after it starts. No other executable hosts loops. The test host skips it through XCTestConfigurationFilePath |
| Ramp flag at launch | ✅ | publishNodFlag(isEnabled(.nod)) reads the cached configuration, or the default (beta 100 / stable 0), synchronously. It also removes the flag when the ramp is off, so the kill switch still applies from the next launch |
--inherit fresh-only, --unattended for composite children |
✅ | resumeArguments passes fresh: false. The no-prompt and prompt paths both default to fresh. The runtime also drops --inherit under --resume (main.ts:112, runtime.ts:109) |
NodStateDirectory removed |
✅ | No references are left. The pane and the launcher now share NodRuntimeLocator.stateDirectory, with the same uppercase uuidString path |
bun test + typecheck ❌ |
Not this PR | src/mcp/daemon.ts(103) TS2322 came in on main with the graph-layer merge (7bdb150f). This PR doesn't touch NodRuntime/, and #601 fixes it (event[key]!) |
Do the tests prove what they claim?
| Test | Fails on main? | Notes |
|---|---|---|
theLineageBriefRidesAFreshLaunchOnly |
✅ Yes | It doesn't compile on main (there's no fresh: parameter), and its logic would fail anyway because main never reads lineage.briefPath. It covers both the fresh-only rule and the composite-child --unattended |
aTimedLoopIsUnattended… (edited) |
✅ | It now gets the brief from lineage, not an argument |
nodHostsNothingWhereItCannotLaunch |
It pins the uninstalled default, which is correct. No test exercises installAvailability() or its truth table (ramp on/off × runtime present/absent). That's understandable, since it's process-global state in a parallel suite, but the PR's core switch has no test |
|
theStateDirectoryIsTheUppercaseNodeID |
Passes on main | It's a refactor guard and doesn't claim RED |
Non-blocking follow-ups
- The pane's own launch skips the new rules.
GhosttyTerminalView.nodLaunchPrefixbuilds argv without lineage: no--inherit, andunattendedonly for.timeBased. Its doc says it uses "the samenodArgumentsthe daemon launches with", but that's no longer true. Any Nod loop the pane starts itself won't get the brief. That includes turn-based forks, which only the pane ever starts, and a pane launch that beats the daemon's launch. It was already missing on main for--inherit; the composite-child--unattendeddifference is new. The pane is the NodWiring work, so it's probably best fixed there. - Size.
.build/nodis about 360 MB (claudealone is 228 MB). Every DMG and update carries it, including stable, where the ramp is 0%.DaemonBootstrapalso copies it into each workspace'sbin. It's worth deciding this on purpose before stable. - Prove the release path once with
make release-dmg SIGN_ID=… NOTARIZE=0, thencodesign --verify --deep --strictand anotarytoolsubmit, before the next beta is cut. That's the only way to confirm the claim about the Anthropic-signed nested binary. macosand the Windows jobs were still pending when I posted this.
A Mac with Nod installed has nod/ramp.on and bin/nod/graphcode-nod in the ~/.graphcode the test host shares, so launch-prefix assertions changed with the machine (CopilotVersionTests.otherBackendsAreUnchanged(.nod) failed). Under XCTest only the explicit overrides count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: scgopi <scgopireddy@gmail.com>
Signed-off-by: scgopi <scgopireddy@gmail.com> # Conflicts: # graphcode/Sources/GraphcodeApp.swift
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.
Final integration for GraphCode Nod, after #583 and #588–#592: Nod now ships in the app bundle and is launchable on installs where its beta ramp is on.
make build-nodpackages NodRuntime;release-dmgcopies it toContents/Helpers/nodand signsgraphcode-nod,copilot-runtimeandruntime.nodewithNodRuntime/packaging/entitlements.plist(JIT).nod.isSpiked= this process could launch it (ramp flag + runtime present). App, daemon and CLI install the check at startup; the test host does not, so hosting tests don't depend on the Mac's own installnod/ramp.onfrom the cached ramp, so a fresh beta install doesn't wait on the fetch--inherit <brief>on fresh launches only; composite children launch--unattendedNodRuntimeLocator.stateDirectory; the app's duplicate helper is removedStill owed, in parallel loops: mounting the graphcode MCP server in both engines (NodMcpMount), and the chat pane's graph slots, delegate actions and card wiring (NodWiring).
Evidence
RED: NodLaunchTests.theLineageBriefRidesAFreshLaunchOnly on origin/main -> compile failure, nodArguments has no fresh: parameter and never passes the lineage brief
GREEN: xcodebuild test (private DerivedData) -> exit 0, 2192 tests in 251 suites passed
GREEN: make build-nod, then bun scripts/smoke.ts copilot with the packaged graphcode-nod and both SDK platform packages hidden -> exit 0, hunk accepted, ask allowed, steer, goal check holds 2/2
REGRESSION: graphcode-cli and graphcoded build -> exit 0; make check -> 0 errors; swift-portable test -> exit 0