Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,23 @@ jobs:
fi
fi

- name: Run CLI version memory guard regression
run: |
set -euo pipefail

CLI_BIN="$(
find "$HOME/Library/Developer/Xcode/DerivedData" -path "*/Build/Products/Debug/cmux" -exec stat -f '%m %N' {} \; \
| sort -nr \
| head -1 \
| cut -d' ' -f2-
)"
if [ -z "${CLI_BIN:-}" ] || [ ! -x "$CLI_BIN" ]; then
echo "cmux CLI binary not found in DerivedData" >&2
exit 1
fi

CMUX_CLI_BIN="$CLI_BIN" python3 tests/test_cli_version_memory_guard.py
Comment on lines +154 to +169

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate cmux-unit scheme =="
SCHEME="$(fd 'cmux-unit\.xcscheme$' . | head -n 1)"
if [ -z "${SCHEME:-}" ]; then
  echo "cmux-unit.xcscheme not found" >&2
  exit 1
fi
echo "$SCHEME"

echo
echo "== Build/Test references inside cmux-unit scheme =="
sed -n '/<BuildAction>/,/<\/BuildAction>/p;/<TestAction/,/<\/TestAction>/p' "$SCHEME" \
  | rg -n 'BlueprintName=|BuildableName='

echo
echo "== Current CI lookup =="
rg -n 'cmux-unit|Build/Products/Debug/cmux' .github/workflows/ci.yml

echo
echo "Expected results:"
echo "1) The scheme output should show a BuildableReference for the cmux product/target, or another explicit path proving the CLI is built."
echo "2) The DerivedData search should be scopeable to GhosttyTabs-* instead of every entry under DerivedData."

Repository: manaflow-ai/cmux

Length of output: 1365


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== All Xcode targets in GhosttyTabs.xcodeproj =="
grep -r 'target "' GhosttyTabs.xcodeproj/project.pbxproj | sed 's/.*target "//' | sed 's/".*//' | sort -u

echo
echo "== Check what test_cli_version_memory_guard.py expects =="
if [ -f tests/test_cli_version_memory_guard.py ]; then
  head -n 50 tests/test_cli_version_memory_guard.py | grep -E 'CMUX_CLI_BIN|argv|cmux' || echo "No direct reference found in first 50 lines"
fi

echo
echo "== Search for CLI or command-line target references =="
rg -i 'cli|command.?line|executable' GhosttyTabs.xcodeproj --type xml | head -n 20

echo
echo "== Check if cmux is defined as a separate product (not app) =="
rg 'BuildableReference.*cmux[^.]' GhosttyTabs.xcodeproj --type xml --context 3

Repository: manaflow-ai/cmux

Length of output: 108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Directory structure of GhosttyTabs.xcodeproj =="
find GhosttyTabs.xcodeproj -type f -name "*.pbxproj" -o -name "*.xcscheme" | head -20

echo
echo "== List all scheme files =="
ls -la GhosttyTabs.xcodeproj/xcshareddata/xcschemes/ 2>/dev/null || echo "No schemes directory found"

echo
echo "== Content of test_cli_version_memory_guard.py =="
if [ -f tests/test_cli_version_memory_guard.py ]; then
  cat tests/test_cli_version_memory_guard.py
else
  echo "File not found at tests/test_cli_version_memory_guard.py"
  echo "Searching for it:"
  find . -name "*test_cli*" -type f 2>/dev/null
fi

echo
echo "== Search for cmux references in all schemes =="
rg -l 'cmux' GhosttyTabs.xcodeproj/xcshareddata/xcschemes/ 2>/dev/null | xargs -I {} sh -c 'echo "File: {}"; rg "BlueprintName|BuildableName" "{}" | head -10'

echo
echo "== Check if cmux (standalone binary) is referenced anywhere in build output =="
rg 'cmux.*product|executable.*cmux' GhosttyTabs.xcodeproj --type xml 2>/dev/null | head -10

Repository: manaflow-ai/cmux

Length of output: 10928


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Full ci.yml content around the regression test (lines 150-170) =="
sed -n '150,170p' .github/workflows/ci.yml

echo
echo "== Check if there are other build steps before this regression test =="
sed -n '90,165p' .github/workflows/ci.yml | grep -E "^\s+-\s+name:|run:" | head -20

echo
echo "== Search for any CLI build or compilation steps =="
rg 'swift build|xcodebuild.*-product|CLI|executable' .github/workflows/ci.yml | head -20

echo
echo "== Check if cmux binary is produced by building the main app =="
rg -A 5 'Build/Products/Debug/cmux' .github/workflows/ci.yml

Repository: manaflow-ai/cmux

Length of output: 2194


The CI workflow will fail because cmux-unit scheme does not produce a standalone CLI binary.

The cmux-unit scheme only builds cmux.app (app bundle) and cmuxTests.xctest (test bundle). The regression test expects a standalone cmux executable at Build/Products/Debug/cmux, which this scheme does not create. The find command at line 158 will not find any matching binary, and the script will exit with code 1 at line 162 before the test even runs.

Either add a separate build step to produce the CLI binary, modify the scheme to build it, or adjust how the binary is resolved.


tests-depot:
# Never run Depot jobs for fork pull requests (avoid billing on external PRs).
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@ jobs:
[[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]]
[[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]]

- name: Run CLI version memory guard regression
run: |
set -euo pipefail
CLI_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux"
[ -x "$CLI_BINARY" ] || { echo "cmux CLI binary not found at $CLI_BINARY" >&2; exit 1; }
CMUX_CLI_BIN="$CLI_BINARY" python3 tests/test_cli_version_memory_guard.py

- name: Check whether build commit is still current main HEAD
if: needs.decide.outputs.should_publish == 'true'
id: current_head
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ jobs:
-clonedSourcePackagesDirPath .spm-cache \
CODE_SIGNING_ALLOWED=NO build

- name: Run CLI version memory guard regression
if: steps.guard_release_assets.outputs.skip_all != 'true'
run: |
set -euo pipefail
CLI_BINARY="build/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux"
[ -x "$CLI_BINARY" ] || { echo "cmux CLI binary not found at $CLI_BINARY" >&2; exit 1; }
CMUX_CLI_BIN="$CLI_BINARY" python3 tests/test_cli_version_memory_guard.py

- name: Inject Sparkle keys into Info.plist
if: steps.guard_release_assets.outputs.skip_all != 'true'
run: |
Expand Down
30 changes: 22 additions & 8 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8172,7 +8172,7 @@ struct CMUXCLI {
}

let fileManager = FileManager.default
var current = executableURL.deletingLastPathComponent()
var current = executableURL.deletingLastPathComponent().standardizedFileURL

while true {
let projectFile = current.appendingPathComponent("GhosttyTabs.xcodeproj/project.pbxproj")
Expand All @@ -8193,8 +8193,7 @@ struct CMUXCLI {
}
}

let parent = current.deletingLastPathComponent()
if parent.path == current.path {
guard let parent = parentSearchURL(for: current) else {
break
}
current = parent
Expand Down Expand Up @@ -8263,6 +8262,22 @@ struct CMUXCLI {
return String(normalized.prefix(12))
}

// Foundation can walk past "/" into "/.." when repeatedly deleting path
// components, so stop once the canonical root is reached.
private func parentSearchURL(for url: URL) -> URL? {
let standardized = url.standardizedFileURL
let path = standardized.path
guard !path.isEmpty, path != "/" else {
return nil
}

let parent = standardized.deletingLastPathComponent().standardizedFileURL
guard parent.path != path else {
return nil
}
return parent
}

private func candidateInfoPlistURLs() -> [URL] {
guard let executableURL = resolvedExecutableURL() else {
return []
Expand All @@ -8280,7 +8295,7 @@ struct CMUXCLI {
candidates.append(url)
}

var current = executableURL.deletingLastPathComponent()
var current = executableURL.deletingLastPathComponent().standardizedFileURL
while true {
if current.pathExtension == "app" {
appendIfExisting(current.appendingPathComponent("Contents/Info.plist"))
Expand All @@ -8299,8 +8314,7 @@ struct CMUXCLI {
break
}

let parent = current.deletingLastPathComponent()
if parent.path == current.path {
guard let parent = parentSearchURL(for: current) else {
break
}
current = parent
Expand All @@ -8313,8 +8327,8 @@ struct CMUXCLI {
}

let searchRoots = [
executableURL.deletingLastPathComponent(),
executableURL.deletingLastPathComponent().deletingLastPathComponent()
executableURL.deletingLastPathComponent().standardizedFileURL,
executableURL.deletingLastPathComponent().deletingLastPathComponent().standardizedFileURL
]
for root in searchRoots {
guard let entries = fileManager.enumerator(
Expand Down
2 changes: 2 additions & 0 deletions tests/test_cli_version_memory_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def build_fixture(root: str, cli_path: str) -> str:
with open(os.path.join(contents_path, "Info.plist"), "wb") as handle:
plistlib.dump(info, handle)

# Regular files are enough here because the fallback scan keys off the
# ".app" suffix before it ever tries to inspect bundle contents.
for index in range(JUNK_APP_COUNT):
open(os.path.join(resources_path, f"junk-{index:05d}.app"), "wb").close()

Expand Down
Loading