launchctl input/output error: Bootstrap failed 5를 10가지로 재현해 찾은 점검 순서
launchctl input/output error는 원인 하나를 가리키지 않았습니다. macOS 27.0.1에서 이미 로드된 서비스, disabled 표시, plist 권한 666, 샌드박스까지 10가지 경우가 모두 같은 에러 5로 끝났습니다. 원인을 가르는 명령을 순서대로 적었습니다.
- macOS
- 자동화
- 트러블슈팅
목차
launchctl bootstrap이 이 두 줄만 남기고 끝날 때가 있습니다.
Bootstrap failed: 5: Input/output error
Try re-running the command as root for richer errors.디스크 고장처럼 읽히는데 디스크와는 상관이 없었습니다. 임시 LaunchAgent로 실패 상황을 하나씩 만들어 보니 서로 다른 10가지 경우가 전부 이 문장으로 끝났습니다. 그래서 검색으로 찾은 해결법 하나를 따라 하기 전에 원인부터 가르는 편이 빠릅니다.
아래는 macOS 27.0.1에서 직접 돌린 결과입니다. plist 작성법과 bootstrap 기본 사용법은 LaunchAgent plist 예제 글에 적어 두었습니다.
샌드박스 탓으로 결론 냈던 날#
7월 26일에 로컬 프록시와 SSH 터널을 LaunchAgent로 올리다가 이 에러를 하루에 세 번 만났습니다. AI 에이전트의 샌드박스 세션에서 launchctl bootstrap, launchctl asuser, launchctl load -w, osascript까지 돌렸는데 전부 실패했습니다.
plist 파일 쓰기는 됐고, 이미 로드된 서비스의 launchctl print도 됐습니다. 파일 권한 문제는 아니라고 보고 "샌드박스가 새 서비스 등록을 막는다"로 정리했습니다.
그 결론에는 허점이 있었습니다. 마지막에 다시 보니 터널 서비스가 state = running, runs = 2로 돌고 있었습니다. 어떤 경로로 등록됐는지는 기록에 없습니다. 지금도 미결입니다.
더 큰 문제는 제가 에러 5를 "샌드박스 에러"로 외워 버린 것입니다. 이번에 샌드박스 밖 터미널 세션(launchctl managername이 Aqua)에서 돌려 보니 같은 문구가 줄줄이 나왔습니다.
10가지 경우가 모두 에러 5로 끝났습니다#
라벨 하나로 상황만 바꿔 가며 돌렸습니다. 아래 표의 결과는 전부 Bootstrap failed: 5: Input/output error, 종료 코드 5입니다.
| 만든 상황 | 대상 도메인 |
|---|---|
| 이미 로드된 라벨을 다시 bootstrap | gui/501 |
| 없는 plist 경로 | gui/501 |
| plist 문법 오류 | gui/501 |
Label 키가 없는 plist |
gui/501 |
launchctl disable로 꺼 둔 라벨 |
gui/501 |
| plist 권한 666 | gui/501 |
도메인을 user/501로 지정 |
user/501 |
root 없이 system 도메인 지정 |
system |
sandbox-exec로 mach-lookup 차단 |
gui/501 |
sandbox-exec로 plist 읽기 차단 |
gui/501 |
에러 5가 나지 않은 경우도 원인을 좁히는 데 쓸모가 있습니다.
- 권한 664는 통과했습니다. 그룹 쓰기는 괜찮고 666만 막혔습니다.
- 없는 도메인
gui/99999는Could not find domain for user gui: 99999, 종료 코드 112로 끝났습니다. - 실행 파일이 없는 plist는
bootstrap이 종료 코드 0으로 성공했습니다. 그 뒤launchctl print에state = spawn scheduled와last exit code = 78: EX_CONFIG가 나왔습니다. 등록 성공과 실행 성공은 별개입니다. bootout직후 곧바로bootstrap해도 종료 코드 0이었습니다. 다만 한 번만 돌렸으니 타이밍 문제가 없다는 증거로 삼지는 않습니다.
launchctl load는 실패해도 종료 코드가 0입니다#
이건 예상하지 못했습니다. 이미 로드된 라벨에 옛 문법을 썼을 때의 출력입니다.
$ launchctl load -w /tmp/lc-test/com.flowoodz.blogtest.plist
Load failed: 5: Input/output error
Try running `launchctl bootstrap` as root for richer errors.
[exit 0]실패 문구를 찍어 놓고 성공으로 끝납니다. 설치 스크립트가 set -e나 &&로 이어져 있어도 그대로 다음 줄로 넘어갑니다.
man launchctl은 load와 unload를 LEGACY SUBCOMMANDS에 두고 bootstrap, bootout, enable, disable을 대안으로 적어 두었습니다.
스크립트에서는 bootstrap을 쓰는 것이 맞습니다. 재현한 건 이미 로드된 경우 하나뿐이라, 다른 실패도 0인지는 확인하지 않았습니다.
점검 순서#
비용이 싼 것부터 봅니다. 라벨과 경로만 바꿔서 그대로 돌리면 됩니다.
LABEL="com.example.foo"
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
UID_NOW="$(id -u)"
# 1. launchd와 대화가 되는 세션인가 (0이면 샌드박스 의심)
launchctl list | wc -l
# 2. 이미 로드돼 있는가 (출력이 나오면 bootout 먼저)
launchctl print "gui/$UID_NOW/$LABEL" | head -3
# 3. 꺼 둔 라벨인가 (disabled면 launchctl enable)
launchctl print-disabled "gui/$UID_NOW" | grep "$LABEL"
# 4. 파일이 멀쩡한가 (없는 파일, 문법 오류는 lint가 잡는다)
plutil -lint "$PLIST"
plutil -extract Label raw "$PLIST"
ls -l "$PLIST"4번에서 plutil -extract를 따로 두는 이유가 있습니다. Label이 없는 plist는 plutil -lint가 OK로 통과합니다. 문법만 보기 때문입니다. -extract는 같은 파일에서 No value at that key path로 실패했습니다.
3번은 놓치기 쉽습니다. disable 표시는 서비스가 내려가 있어도 그대로 남아서, 파일이 멀쩡해도 bootstrap이 막힙니다.
제 맥에는 ~/Library/LaunchAgents에 plist가 58개 있고 자동화 대시보드의 끄기 스위치가 launchctl disable이라 남 일이 아닙니다.
시스템 로그에 진짜 이유가 남습니다#
처음에는 로그에 아무것도 없는 줄 알았습니다. log show가 빈 결과만 돌려줬습니다. 알고 보니 zsh에서 log는 내장 명령이라 too many arguments로 실패하고 있었고, 그 에러를 2>/dev/null로 제가 버리고 있었습니다. 절대 경로로 부르면 됩니다.
/usr/bin/log show --last 1m --style compact \
--predicate 'process == "launchd" AND eventMessage CONTAINS "foo.plist"'권한 666과 문법 오류는 터미널에서 똑같던 에러가 여기서 갈렸습니다.
E launchd: [gui/501/com.flowoodz.blogtest:] Caller specified a plist with bad ownership/permissions: path = /private/tmp/lc-test2/w.plist
E launchd: [gui/501:] Could not parse plist: path = /private/tmp/lc-test2/bad.plist, error = 109: Invalid property list줄 앞의 시각과 PID는 잘라 냈습니다. 없는 파일과 이미 로드된 경우는 같은 필터로 잡히는 줄이 없었습니다. 그 둘은 2번과 4번 명령이 답합니다.
샌드박스인지 가르는 한 줄#
launchctl list | wc -l이 평소에는 596이었고, sandbox-exec로 mach-lookup을 막으면 0이었습니다. 7월 31일 기록에도 에이전트 샌드박스에서 launchctl list가 아무것도 출력하지 않았다고 남아 있습니다. 증상은 맞습니다.
다만 sandbox-exec는 제가 흉내 낸 프로필이고, 그 에이전트 샌드박스의 실제 규칙은 확인하지 못했습니다. 0줄이 나오면 파일 권한이나 전체 디스크 접근을 뒤지지 말고 일반 터미널에서 같은 명령을 한 번 돌려 보는 것이 빠릅니다.
아직 못 푼 것#
- 에러 문구가 권하는 root 재실행은 못 해 봤습니다. 무인 세션이라
sudo가a password is required로 멈췄습니다. 얼마나 자세한 이유가 나오는지는 미결입니다. user/501이 왜 실패했는지 모릅니다. 로그인 세션 작업은gui/에 올리면 되니 더 파지 않았습니다.- 위 결과는 macOS 27.0.1 한 대의 결과입니다. 다른 버전에서 같은지는 확인하지 않았습니다.
테스트 흔적도 하나 남았습니다. 서비스와 임시 파일은 지웠는데 launchctl print-disabled에 "com.flowoodz.blogtest" => enabled 한 줄이 그대로 있습니다. disable을 한 번 쓰면 라벨 기록이 남습니다.