Require launch tag for cmux DEV

This commit is contained in:
Lawrence Chen 2026-02-24 20:39:55 -08:00
parent 7a4d986d85
commit f617032ad5
5 changed files with 117 additions and 4 deletions

View file

@ -163,6 +163,8 @@ struct SocketControlSettings {
static let legacyEnabledKey = "socketControlEnabled"
static let allowSocketPathOverrideKey = "CMUX_ALLOW_SOCKET_OVERRIDE"
static let socketPasswordEnvKey = "CMUX_SOCKET_PASSWORD"
static let launchTagEnvKey = "CMUX_TAG"
static let baseDebugBundleIdentifier = "com.cmuxterm.app.debug"
private static func normalizeMode(_ raw: String) -> String {
raw
@ -211,6 +213,53 @@ struct SocketControlSettings {
#endif
}
static func launchTag(
environment: [String: String] = ProcessInfo.processInfo.environment
) -> String? {
guard let raw = environment[launchTagEnvKey] else { return nil }
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? nil : trimmed
}
static func shouldBlockUntaggedDebugLaunch(
environment: [String: String] = ProcessInfo.processInfo.environment,
bundleIdentifier: String? = Bundle.main.bundleIdentifier,
isDebugBuild: Bool = SocketControlSettings.isDebugBuild
) -> Bool {
guard isDebugBuild else { return false }
if isRunningUnderXCTest(environment: environment) {
return false
}
guard let bundleIdentifier = bundleIdentifier?.trimmingCharacters(in: .whitespacesAndNewlines),
!bundleIdentifier.isEmpty else {
return false
}
if bundleIdentifier.hasPrefix("\(baseDebugBundleIdentifier).") {
return false
}
guard bundleIdentifier == baseDebugBundleIdentifier else {
return false
}
return launchTag(environment: environment) == nil
}
static func isRunningUnderXCTest(environment: [String: String]) -> Bool {
let indicators = [
"XCTestConfigurationFilePath",
"XCTestBundlePath",
"XCTestSessionIdentifier",
"XCInjectBundleInto",
]
return indicators.contains { key in
guard let value = environment[key] else { return false }
return !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
}
static func socketPath(
environment: [String: String] = ProcessInfo.processInfo.environment,
bundleIdentifier: String? = Bundle.main.bundleIdentifier,

View file

@ -35,6 +35,10 @@ struct cmuxApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
init() {
if SocketControlSettings.shouldBlockUntaggedDebugLaunch() {
Self.terminateForMissingLaunchTag()
}
Self.configureGhosttyEnvironment()
let startupAppearance = AppearanceSettings.resolvedMode()
@ -58,6 +62,14 @@ struct cmuxApp: App {
appDelegate.configure(tabManager: tabManager, notificationStore: notificationStore, sidebarState: sidebarState)
}
private static func terminateForMissingLaunchTag() -> Never {
let message = "error: refusing to launch untagged cmux DEV; start with ./scripts/reload.sh --tag <name> (or set CMUX_TAG for test harnesses)"
fputs("\(message)\n", stderr)
fflush(stderr)
NSLog("%@", message)
Darwin.exit(64)
}
private static func configureGhosttyEnvironment() {
let fileManager = FileManager.default
let ghosttyAppResources = "/Applications/Ghostty.app/Contents/Resources/ghostty"

View file

@ -691,6 +691,56 @@ final class SocketControlSettingsTests: XCTestCase {
"/tmp/cmux-staging.sock"
)
}
func testUntaggedDebugBundleBlockedWithoutLaunchTag() {
XCTAssertTrue(
SocketControlSettings.shouldBlockUntaggedDebugLaunch(
environment: [:],
bundleIdentifier: "com.cmuxterm.app.debug",
isDebugBuild: true
)
)
}
func testUntaggedDebugBundleAllowedWithLaunchTag() {
XCTAssertFalse(
SocketControlSettings.shouldBlockUntaggedDebugLaunch(
environment: ["CMUX_TAG": "tests-v1"],
bundleIdentifier: "com.cmuxterm.app.debug",
isDebugBuild: true
)
)
}
func testTaggedDebugBundleAllowedWithoutLaunchTag() {
XCTAssertFalse(
SocketControlSettings.shouldBlockUntaggedDebugLaunch(
environment: [:],
bundleIdentifier: "com.cmuxterm.app.debug.tests-v1",
isDebugBuild: true
)
)
}
func testReleaseBuildIgnoresLaunchTagGate() {
XCTAssertFalse(
SocketControlSettings.shouldBlockUntaggedDebugLaunch(
environment: [:],
bundleIdentifier: "com.cmuxterm.app.debug",
isDebugBuild: false
)
)
}
func testXCTestLaunchIgnoresLaunchTagGate() {
XCTAssertFalse(
SocketControlSettings.shouldBlockUntaggedDebugLaunch(
environment: ["XCTestConfigurationFilePath": "/tmp/fake.xctestconfiguration"],
bundleIdentifier: "com.cmuxterm.app.debug",
isDebugBuild: true
)
)
}
}
final class PostHogAnalyticsPropertiesTests: XCTestCase {

View file

@ -13,6 +13,7 @@ cd "$(dirname "$0")/.."
DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData/cmux-tests-v1"
APP="$DERIVED_DATA_PATH/Build/Products/Debug/cmux DEV.app"
RUN_TAG="tests-v1"
echo "== build =="
# Work around stale explicit-module cache artifacts (notably Sentry headers) that can
@ -51,7 +52,7 @@ launch_and_wait() {
defaults write com.cmuxterm.app.debug socketControlMode -string full >/dev/null 2>&1 || true
# Launch directly with UI test mode enabled so startup follows deterministic test codepaths.
CMUX_UI_TEST_MODE=1 "$APP/Contents/MacOS/cmux DEV" >/dev/null 2>&1 &
CMUX_TAG="$RUN_TAG" CMUX_UI_TEST_MODE=1 "$APP/Contents/MacOS/cmux DEV" >/dev/null 2>&1 &
SOCK=""
for _ in {1..120}; do
@ -70,7 +71,7 @@ launch_and_wait() {
export CMUX_SOCKET="$SOCK"
# Ensure LaunchServices has a visible/main window attached for rendering checks.
open "$APP" >/dev/null 2>&1 || true
CMUX_TAG="$RUN_TAG" open "$APP" >/dev/null 2>&1 || true
sleep 0.5
echo "== wait ready =="

View file

@ -13,6 +13,7 @@ cd "$(dirname "$0")/.."
DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData/cmux-tests-v2"
APP="$DERIVED_DATA_PATH/Build/Products/Debug/cmux DEV.app"
RUN_TAG="tests-v2"
echo "== build =="
# Work around stale explicit-module cache artifacts (notably Sentry headers) that can
@ -51,7 +52,7 @@ launch_and_wait() {
defaults write com.cmuxterm.app.debug socketControlMode -string full >/dev/null 2>&1 || true
# Launch directly with UI test mode enabled so startup follows deterministic test codepaths.
CMUX_UI_TEST_MODE=1 "$APP/Contents/MacOS/cmux DEV" >/dev/null 2>&1 &
CMUX_TAG="$RUN_TAG" CMUX_UI_TEST_MODE=1 "$APP/Contents/MacOS/cmux DEV" >/dev/null 2>&1 &
SOCK=""
for _ in {1..120}; do
@ -70,7 +71,7 @@ launch_and_wait() {
export CMUX_SOCKET="$SOCK"
# Ensure LaunchServices has a visible/main window attached for rendering checks.
open "$APP" >/dev/null 2>&1 || true
CMUX_TAG="$RUN_TAG" open "$APP" >/dev/null 2>&1 || true
sleep 0.5
echo "== wait ready =="