fix: reset unsigned launchd overrides (#695) (thanks @jeffersonwarrior)

This commit is contained in:
Peter Steinberger
2026-01-11 03:19:24 +01:00
parent 325ed80252
commit 9b6bc0e66b
5 changed files with 85 additions and 3 deletions

View File

@@ -8,6 +8,7 @@
- CLI/Status: make `status --all` scan progress determinate (OSC progress + spinner).
- Terminal/Table: ANSI-safe wrapping to prevent table clipping/color loss; add regression coverage.
- CLI/Update: gate progress spinner on stdout TTY and align clean-check step label. (#701) — thanks @bjesuiter.
- macOS: clear unsigned launchd overrides on signed restarts and warn via doctor when attach-only/disable markers are set. (#695) — thanks @jeffersonwarrior.
## 2026.1.11-4

View File

@@ -44,6 +44,22 @@ Steps:
The UI should show “Using existing gateway …” once connected.
## Unsigned dev builds
`scripts/restart-mac.sh --no-sign` is for fast local builds when you dont have
signing keys. To prevent launchd from pointing at an unsigned relay binary, it:
- Writes `~/.clawdbot/disable-launchagent`.
- Sets `clawdbot.gateway.attachExistingOnly=true` in the macOS app defaults.
Signed runs of `scripts/restart-mac.sh` clear these overrides if the marker is
present. To reset manually:
```bash
rm ~/.clawdbot/disable-launchagent
defaults write com.clawdbot.mac clawdbot.gateway.attachExistingOnly -bool NO
```
## Remote mode
Remote mode never starts a local Gateway. The app uses an SSH tunnel to the

View File

@@ -19,6 +19,7 @@ NO_SIGN=0
SIGN=0
AUTO_DETECT_SIGNING=1
GATEWAY_WAIT_SECONDS="${CLAWDBOT_GATEWAY_WAIT_SECONDS:-0}"
LAUNCHAGENT_DISABLE_MARKER="${HOME}/.clawdbot/disable-launchagent"
log() { printf '%s\n' "$*"; }
fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
@@ -94,6 +95,10 @@ for arg in "$@"; do
log " node dist/entry.js daemon install --force --runtime node"
log " node dist/entry.js daemon restart"
log ""
log "Reset unsigned overrides:"
log " rm ~/.clawdbot/disable-launchagent"
log " defaults write <bundle-id> clawdbot.gateway.attachExistingOnly -bool NO"
log ""
log "Default behavior: Auto-detect signing keys, fallback to --no-sign if none found"
exit 0
;;
@@ -163,7 +168,7 @@ if [ "$NO_SIGN" -eq 1 ]; then
export ALLOW_ADHOC_SIGNING=1
export SIGN_IDENTITY="-"
mkdir -p "${HOME}/.clawdbot"
run_step "disable launchagent writes" /usr/bin/touch "${HOME}/.clawdbot/disable-launchagent"
run_step "disable launchagent writes" /usr/bin/touch "${LAUNCHAGENT_DISABLE_MARKER}"
elif [ "$SIGN" -eq 1 ]; then
if ! check_signing_keys; then
fail "No signing identity found. Use --no-sign or install a signing key."
@@ -198,13 +203,20 @@ choose_app_bundle() {
choose_app_bundle
APP_BUNDLE_ID="$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${APP_BUNDLE}/Contents/Info.plist" 2>/dev/null || true)"
# When unsigned, avoid the app overwriting the LaunchAgent with the relay binary.
if [ "$NO_SIGN" -eq 1 ]; then
APP_BUNDLE_ID="$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${APP_BUNDLE}/Contents/Info.plist" 2>/dev/null || true)"
if [[ -n "${APP_BUNDLE_ID}" ]]; then
run_step "set attach-existing-only" \
/usr/bin/defaults write "${APP_BUNDLE_ID}" clawdbot.gateway.attachExistingOnly -bool YES
fi
elif [[ -f "${LAUNCHAGENT_DISABLE_MARKER}" ]]; then
run_step "clear launchagent disable marker" /bin/rm -f "${LAUNCHAGENT_DISABLE_MARKER}"
if [[ -n "${APP_BUNDLE_ID}" ]]; then
run_step "unset attach-existing-only" \
/usr/bin/defaults write "${APP_BUNDLE_ID}" clawdbot.gateway.attachExistingOnly -bool NO
fi
fi
# 4) Launch the installed app in the foreground so the menu bar extra appears.

View File

@@ -774,7 +774,10 @@ describe("doctor", () => {
const docker = sandbox.docker as Record<string, unknown>;
expect(docker.image).toBe("clawdis-sandbox-common:bookworm-slim");
expect(runCommandWithTimeout).not.toHaveBeenCalled();
const defaultsCalls = runCommandWithTimeout.mock.calls.filter(
([args]) => Array.isArray(args) && args[0] === "/usr/bin/defaults",
);
expect(defaultsCalls.length).toBe(runCommandWithTimeout.mock.calls.length);
});
it("runs legacy state migrations in non-interactive mode without prompting", async () => {

View File

@@ -1,3 +1,5 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import {
intro as clackIntro,
@@ -135,6 +137,53 @@ function noteOpencodeProviderOverrides(cfg: ClawdbotConfig) {
note(lines.join("\n"), "OpenCode Zen");
}
const MAC_APP_BUNDLE_ID = "com.clawdbot.mac";
const MAC_ATTACH_EXISTING_ONLY_KEY = "clawdbot.gateway.attachExistingOnly";
function resolveHomeDir(): string {
return process.env.HOME ?? os.homedir();
}
async function readMacAttachExistingOnly(): Promise<boolean | null> {
const result = await runCommandWithTimeout(
[
"/usr/bin/defaults",
"read",
MAC_APP_BUNDLE_ID,
MAC_ATTACH_EXISTING_ONLY_KEY,
],
{ timeoutMs: 2000 },
).catch(() => null);
if (!result || result.code !== 0) return null;
const raw = result.stdout.trim().toLowerCase();
if (["1", "true", "yes"].includes(raw)) return true;
if (["0", "false", "no"].includes(raw)) return false;
return null;
}
async function noteMacLaunchAgentOverrides() {
if (process.platform !== "darwin") return;
const markerPath = path.join(
resolveHomeDir(),
".clawdbot",
"disable-launchagent",
);
const hasMarker = fs.existsSync(markerPath);
const attachOnly = await readMacAttachExistingOnly();
if (!hasMarker && attachOnly !== true) return;
const lines = [
hasMarker ? `- LaunchAgent writes are disabled via ${markerPath}.` : null,
attachOnly === true
? `- macOS app is set to Attach-only (${MAC_APP_BUNDLE_ID}:${MAC_ATTACH_EXISTING_ONLY_KEY}=true).`
: null,
"- To restore default behavior:",
` rm ${markerPath}`,
` defaults write ${MAC_APP_BUNDLE_ID} ${MAC_ATTACH_EXISTING_ONLY_KEY} -bool NO`,
].filter((line): line is string => Boolean(line));
note(lines.join("\n"), "Gateway (macOS)");
}
async function detectClawdbotGitCheckout(
root: string,
): Promise<"git" | "not-git" | "unknown"> {
@@ -368,6 +417,7 @@ export async function doctorCommand(
runtime,
prompter,
);
await noteMacLaunchAgentOverrides();
await noteSecurityWarnings(cfg);