From 960006e6d6c5b53a42d3a46e581ba363d4487eaf Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:45:48 -0700 Subject: [PATCH] reload.sh: default to build-only, add --launch flag (#2097) * reload.sh: default to build-only, add --launch flag to open app By default, reload.sh now builds and prints the app path without launching. Pass --launch to get the previous behavior (kill existing instance and open). This lets agents build without stealing focus, and the user can cmd-click the printed path to launch when ready. * CLAUDE.md: use reload.sh output for app path instead of hardcoded home dir The templates hardcoded /Users/lawrencechen/ which broke cmd-click on machines with a different home directory. Agents now read the actual path from reload.sh's "App path:" output. * CLAUDE.md: add concrete example for app path URL format Uses a fictional /Users/jane/ to make it clear the path comes from reload.sh output, not a hardcoded value. * CLAUDE.md: clearer step-by-step instructions for app path URL Explicit 3-step recipe (grab path, prepend file://, format as link) with example showing the reload.sh output and the expected result. --------- Co-authored-by: Lawrence Chen --- CLAUDE.md | 35 ++++++++--- CONTRIBUTING.md | 7 ++- scripts/reload.sh | 149 ++++++++++++++++++++++++++-------------------- 3 files changed, 115 insertions(+), 76 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8a8e4e0a..fe961481 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,31 +10,47 @@ Run the setup script to initialize submodules and build GhosttyKit: ## Local dev -After making code changes, always run the reload script with a tag to launch the Debug app: +After making code changes, always run the reload script with a tag to build the Debug app: ```bash ./scripts/reload.sh --tag fix-zsh-autosuggestions ``` -When reporting a tagged reload result in chat, use the format for your agent type: +By default, `reload.sh` builds but does **not** launch the app. The script prints the `.app` path so the user can cmd-click to open it. Pass `--launch` to kill any existing instance and open the app automatically: -**Claude Code** (markdown link with correct derived-data path, cmd+clickable): +```bash +./scripts/reload.sh --tag fix-zsh-autosuggestions --launch +``` + +`reload.sh` prints an `App path:` line with the absolute path to the built `.app`. Use that path to build a cmd-clickable `file://` URL. Steps: + +1. Grab the path from the `App path:` line in `reload.sh` output. +2. Prepend `file://` and URL-encode spaces as `%20`. Do not hardcode any part of the path. +3. Format it as a markdown link using the template for your agent type. + +Example. If `reload.sh` output contains: +``` +App path: + /Users/someone/Library/Developer/Xcode/DerivedData/cmux-my-tag/Build/Products/Debug/cmux DEV my-tag.app +``` + +**Claude Code** outputs: ```markdown ======================================================= -[cmux DEV .app](file:///Users/lawrencechen/Library/Developer/Xcode/DerivedData/cmux-/Build/Products/Debug/cmux%20DEV%20.app) +[cmux DEV my-tag.app](file:///Users/someone/Library/Developer/Xcode/DerivedData/cmux-my-tag/Build/Products/Debug/cmux%20DEV%20my-tag.app) ======================================================= ``` -**Codex** (plain text format): +**Codex** outputs: ``` ======================================================= -[: file:///Users/lawrencechen/Library/Developer/Xcode/DerivedData/cmux-/Build/Products/Debug/cmux%20DEV%20.app](file:///Users/lawrencechen/Library/Developer/Xcode/DerivedData/cmux-/Build/Products/Debug/cmux%20DEV%20.app) +[my-tag: file:///Users/someone/Library/Developer/Xcode/DerivedData/cmux-my-tag/Build/Products/Debug/cmux%20DEV%20my-tag.app](file:///Users/someone/Library/Developer/Xcode/DerivedData/cmux-my-tag/Build/Products/Debug/cmux%20DEV%20my-tag.app) ======================================================= ``` -Never use `/tmp/cmux-/...` app links in chat output. If the expected DerivedData path is missing, resolve the real `.app` path and report that `file://` URL. +Never use `/tmp/cmux-/...` app links in chat output. -After making code changes, always use `reload.sh --tag` to build and launch. **Never run bare `xcodebuild` or `open` an untagged `cmux DEV.app`.** Untagged builds share the default debug socket and bundle ID with other agents, causing conflicts and stealing focus. +After making code changes, always use `reload.sh --tag` to build. **Never run bare `xcodebuild` or `open` an untagged `cmux DEV.app`.** Untagged builds share the default debug socket and bundle ID with other agents, causing conflicts and stealing focus. ```bash ./scripts/reload.sh --tag @@ -58,10 +74,11 @@ When rebuilding cmuxd for release/bundling, always use ReleaseFast: cd cmuxd && zig build -Doptimize=ReleaseFast ``` -`reload` = kill and launch the Debug app only (tag required): +`reload` = build the Debug app (tag required). Pass `--launch` to also kill existing and open: ```bash ./scripts/reload.sh --tag +./scripts/reload.sh --tag --launch ``` `reloadp` = kill and launch the Release app: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59512b25..49d681fa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,17 +24,18 @@ - Build the GhosttyKit.xcframework from source - Create the necessary symlinks -3. Build and run the debug app: +3. Build the debug app: ```bash - ./scripts/reload.sh + ./scripts/reload.sh --tag my-feature ``` + The script prints the `.app` path. Cmd-click to open, or pass `--launch` to open automatically. ## Development Scripts | Script | Description | |--------|-------------| | `./scripts/setup.sh` | One-time setup (submodules + xcframework) | -| `./scripts/reload.sh` | Build and launch Debug app | +| `./scripts/reload.sh` | Build Debug app (pass `--launch` to also open it) | | `./scripts/reloadp.sh` | Build and launch Release app | | `./scripts/reload2.sh` | Reload both Debug and Release | | `./scripts/rebuild.sh` | Clean rebuild | diff --git a/scripts/reload.sh b/scripts/reload.sh index 9ea4a881..3c414353 100755 --- a/scripts/reload.sh +++ b/scripts/reload.sh @@ -9,6 +9,7 @@ NAME_SET=0 BUNDLE_SET=0 DERIVED_SET=0 TAG="" +LAUNCH=0 CMUX_DEBUG_LOG="" CLI_PATH="" LAST_SOCKET_PATH_DIR="$HOME/Library/Application Support/cmux" @@ -106,6 +107,8 @@ Usage: ./scripts/reload.sh --tag [options] Options: --tag Required. Short tag for parallel builds (e.g., feature-xyz-lol). Sets app name, bundle id, and derived data path unless overridden. + --launch Launch the app after building. Without this flag, the script + builds and prints the app path but does not open it. --name Override app display/bundle name. --bundle-id Override bundle identifier. --derived-data Override derived data path. @@ -224,6 +227,10 @@ while [[ $# -gt 0 ]]; do BUNDLE_SET=1 shift 2 ;; + --launch) + LAUNCH=1 + shift + ;; --derived-data) DERIVED_DATA="${2:-}" if [[ -z "$DERIVED_DATA" ]]; then @@ -408,17 +415,7 @@ if [[ -x "$CLI_PATH" ]]; then fi fi -# Ensure any running instance is fully terminated, regardless of DerivedData path. -/usr/bin/osascript -e "tell application id \"${BUNDLE_ID}\" to quit" >/dev/null 2>&1 || true -sleep 0.3 -if [[ -z "$TAG" ]]; then - # Non-tag mode: kill any running instance (across any DerivedData path) to avoid socket conflicts. - pkill -f "/${BASE_APP_NAME}.app/Contents/MacOS/${BASE_APP_NAME}" || true -else - # Tag mode: only kill the tagged instance; allow side-by-side with the main app. - pkill -f "${APP_NAME}.app/Contents/MacOS/${BASE_APP_NAME}" || true -fi -sleep 0.3 +# Build cmuxd and ghostty helper binaries (needed for both launch and no-launch). CMUXD_SRC="$PWD/cmuxd/zig-out/bin/cmuxd" GHOSTTY_HELPER_SRC="$PWD/ghostty/zig-out/bin/ghostty" if [[ -d "$PWD/cmuxd" ]]; then @@ -443,62 +440,81 @@ CLI_PATH="$APP_PATH/Contents/Resources/bin/cmux" if [[ -x "$CLI_PATH" ]]; then echo "$CLI_PATH" > /tmp/cmux-last-cli-path || true fi -# Avoid inheriting cmux/ghostty environment variables from the terminal that -# runs this script (often inside another cmux instance), which can cause -# socket and resource-path conflicts. -OPEN_CLEAN_ENV=( - env - -u CMUX_SOCKET_PATH - -u CMUX_WORKSPACE_ID - -u CMUX_SURFACE_ID - -u CMUX_TAB_ID - -u CMUX_PANEL_ID - -u CMUXD_UNIX_PATH - -u CMUX_TAG - -u CMUX_DEBUG_LOG - -u CMUX_BUNDLE_ID - -u CMUX_SHELL_INTEGRATION - -u GHOSTTY_BIN_DIR - -u GHOSTTY_RESOURCES_DIR - -u GHOSTTY_SHELL_FEATURES - # Dev shells (including CI/Codex) often force-disable paging by exporting these. - # Don't leak that into cmux, otherwise `git diff` won't page even with PAGER=less. - -u GIT_PAGER - -u GH_PAGER - -u TERMINFO - -u XDG_DATA_DIRS -) -if [[ -n "${TAG_SLUG:-}" && -n "${CMUX_SOCKET:-}" ]]; then - # Ensure tag-specific socket paths win even if the caller has CMUX_* overrides. - "${OPEN_CLEAN_ENV[@]}" CMUX_TAG="$TAG_SLUG" CMUX_SOCKET_ENABLE=1 CMUX_SOCKET_MODE=automation CMUX_SOCKET_PATH="$CMUX_SOCKET" CMUXD_UNIX_PATH="$CMUXD_SOCKET" CMUX_DEBUG_LOG="$CMUX_DEBUG_LOG" CMUX_REMOTE_DAEMON_ALLOW_LOCAL_BUILD=1 CMUXTERM_REPO_ROOT="$PWD" open -g "$APP_PATH" -elif [[ -n "${TAG_SLUG:-}" ]]; then - "${OPEN_CLEAN_ENV[@]}" CMUX_TAG="$TAG_SLUG" CMUX_SOCKET_ENABLE=1 CMUX_SOCKET_MODE=automation CMUX_DEBUG_LOG="$CMUX_DEBUG_LOG" CMUX_REMOTE_DAEMON_ALLOW_LOCAL_BUILD=1 CMUXTERM_REPO_ROOT="$PWD" open -g "$APP_PATH" -else - echo "/tmp/cmux-debug.sock" > /tmp/cmux-last-socket-path || true - echo "/tmp/cmux-debug.log" > /tmp/cmux-last-debug-log-path || true - "${OPEN_CLEAN_ENV[@]}" open -g "$APP_PATH" +if [[ "$LAUNCH" -eq 1 ]]; then + # Ensure any running instance is fully terminated, regardless of DerivedData path. + /usr/bin/osascript -e "tell application id \"${BUNDLE_ID}\" to quit" >/dev/null 2>&1 || true + sleep 0.3 + if [[ -z "$TAG" ]]; then + # Non-tag mode: kill any running instance (across any DerivedData path) to avoid socket conflicts. + pkill -f "/${BASE_APP_NAME}.app/Contents/MacOS/${BASE_APP_NAME}" || true + else + # Tag mode: only kill the tagged instance; allow side-by-side with the main app. + pkill -f "${APP_NAME}.app/Contents/MacOS/${BASE_APP_NAME}" || true + fi + sleep 0.3 + + # Avoid inheriting cmux/ghostty environment variables from the terminal that + # runs this script (often inside another cmux instance), which can cause + # socket and resource-path conflicts. + OPEN_CLEAN_ENV=( + env + -u CMUX_SOCKET_PATH + -u CMUX_WORKSPACE_ID + -u CMUX_SURFACE_ID + -u CMUX_TAB_ID + -u CMUX_PANEL_ID + -u CMUXD_UNIX_PATH + -u CMUX_TAG + -u CMUX_DEBUG_LOG + -u CMUX_BUNDLE_ID + -u CMUX_SHELL_INTEGRATION + -u GHOSTTY_BIN_DIR + -u GHOSTTY_RESOURCES_DIR + -u GHOSTTY_SHELL_FEATURES + # Dev shells (including CI/Codex) often force-disable paging by exporting these. + # Don't leak that into cmux, otherwise `git diff` won't page even with PAGER=less. + -u GIT_PAGER + -u GH_PAGER + -u TERMINFO + -u XDG_DATA_DIRS + ) + + if [[ -n "${TAG_SLUG:-}" && -n "${CMUX_SOCKET:-}" ]]; then + # Ensure tag-specific socket paths win even if the caller has CMUX_* overrides. + "${OPEN_CLEAN_ENV[@]}" CMUX_TAG="$TAG_SLUG" CMUX_SOCKET_ENABLE=1 CMUX_SOCKET_MODE=automation CMUX_SOCKET_PATH="$CMUX_SOCKET" CMUXD_UNIX_PATH="$CMUXD_SOCKET" CMUX_DEBUG_LOG="$CMUX_DEBUG_LOG" CMUX_REMOTE_DAEMON_ALLOW_LOCAL_BUILD=1 CMUXTERM_REPO_ROOT="$PWD" open -g "$APP_PATH" + elif [[ -n "${TAG_SLUG:-}" ]]; then + "${OPEN_CLEAN_ENV[@]}" CMUX_TAG="$TAG_SLUG" CMUX_SOCKET_ENABLE=1 CMUX_SOCKET_MODE=automation CMUX_DEBUG_LOG="$CMUX_DEBUG_LOG" CMUX_REMOTE_DAEMON_ALLOW_LOCAL_BUILD=1 CMUXTERM_REPO_ROOT="$PWD" open -g "$APP_PATH" + else + echo "/tmp/cmux-debug.sock" > /tmp/cmux-last-socket-path || true + echo "/tmp/cmux-debug.log" > /tmp/cmux-last-debug-log-path || true + "${OPEN_CLEAN_ENV[@]}" open -g "$APP_PATH" + fi + + # Safety: ensure only one instance is running. + sleep 0.2 + PIDS=($(pgrep -f "${APP_PATH}/Contents/MacOS/" || true)) + if [[ "${#PIDS[@]}" -gt 1 ]]; then + NEWEST_PID="" + NEWEST_AGE=999999 + for PID in "${PIDS[@]}"; do + AGE="$(ps -o etimes= -p "$PID" | tr -d ' ')" + if [[ -n "$AGE" && "$AGE" -lt "$NEWEST_AGE" ]]; then + NEWEST_AGE="$AGE" + NEWEST_PID="$PID" + fi + done + for PID in "${PIDS[@]}"; do + if [[ "$PID" != "$NEWEST_PID" ]]; then + kill "$PID" 2>/dev/null || true + fi + done + fi fi -# Safety: ensure only one instance is running. -sleep 0.2 -PIDS=($(pgrep -f "${APP_PATH}/Contents/MacOS/" || true)) -if [[ "${#PIDS[@]}" -gt 1 ]]; then - NEWEST_PID="" - NEWEST_AGE=999999 - for PID in "${PIDS[@]}"; do - AGE="$(ps -o etimes= -p "$PID" | tr -d ' ')" - if [[ -n "$AGE" && "$AGE" -lt "$NEWEST_AGE" ]]; then - NEWEST_AGE="$AGE" - NEWEST_PID="$PID" - fi - done - for PID in "${PIDS[@]}"; do - if [[ "$PID" != "$NEWEST_PID" ]]; then - kill "$PID" 2>/dev/null || true - fi - done -fi +echo +echo "App path:" +echo " $APP_PATH" if [[ -n "${TAG_SLUG:-}" ]]; then print_tag_cleanup_reminder "$TAG_SLUG" @@ -516,3 +532,8 @@ if [[ -x "${CLI_PATH:-}" ]]; then fi echo "If your shell still resolves the old cmux, run: rehash" fi + +if [[ "$LAUNCH" -eq 0 ]]; then + echo + echo "Build complete. Pass --launch to open the app, or cmd-click the path above." +fi