어떤 일이 있었나
OpenCode용 커스텀 로컬 플러그인을 설정하고 있었습니다. 설정을 깔끔하고 이식성 있게 유지하고 싶어서(적어도 그때는 그렇게 생각했습니다), 플러그인 경로에 홈 디렉토리를 나타내는 물결표(~)를 사용했습니다.
제 opencode.json 설정은 다음과 같았습니다.
{
"plugins": [
"~/.local/share/oh-my-opencode"
]
}
하지만 OpenCode를 실행하자마자 BunInstallFailedError라는 당혹스러운 에러와 함께 실패했습니다. 에러 메시지는 다음과 같았습니다.
{
"name": "BunInstallFailedError",
"data": {
"pkg": "~/.local/share/oh-my-opencode",
"version": "latest"
}
}
왜 로컬 경로를 마치 npm 패키지인 것처럼 “설치"하려고 하는 걸까요?
근본 원인
근본 원인은 OpenCode가 설정 파일의 경로에 대해 쉘 확장(shell expansion)을 수행하지 않기 때문입니다.
터미널에서는 쉘(bash, zsh 등)이 ~를 자동으로 전체 홈 디렉토리 경로(예: /Users/koed)로 확장해 줍니다. 하지만 프로그램이 JSON 파일을 읽을 때는 "~/.local/share/oh-my-opencode"라는 문자열을 있는 그대로 보게 됩니다.
OpenCode는 이 경로를 파일 시스템상의 유효한 절대 경로로 인식하지 못했고, 이를 패키지 이름으로 간주하여 패키지 매니저(Bun)에게 설치를 요청했습니다. 당연히 Bun은 레지스트리에서 ~/.local/share/oh-my-opencode라는 이름의 패키지를 찾을 수 없었고, 결국 설치 실패로 이어진 것입니다.
해결 방법
해결 방법은 물결표 대신 플러그인 디렉토리의 전체 절대 경로를 사용하는 것이었습니다.
// 수정 전
"plugins": ["~/.local/share/oh-my-opencode"]
// 수정 후
"plugins": ["/Users/koed/.local/share/oh-my-opencode"]
절대 경로를 제공하자 OpenCode는 이를 로컬 디렉토리로 올바르게 인식했고, 외부 레지스트리에 접근하려는 시도 없이 플러그인을 성공적으로 로드했습니다.
배운 점
- 설정 파일에서 쉘 기능을 기대하지 마세요. 문서에 물결표 확장이나 환경 변수를 지원한다고 명시되어 있지 않는 한, 프로그램은 문자열을 리터럴(literal)로 처리한다고 가정해야 합니다.
- 절대 경로가 가장 안전합니다. 상대 경로가 작동하는 경우도 있지만, 절대 경로를 사용하면 애플리케이션이 실행되는 작업 디렉토리에 상관없이 모호함을 제거할 수 있습니다.
- 폴백(fallback) 동작을 이해하세요. 이번 경우에는 유효하지 않은 경로를 패키지 이름으로 간주하는 폴백 동작 때문에 에러 메시지가 훨씬 더 혼란스러워졌습니다.
- JSON은 데이터일 뿐입니다. JSON 자체에는 “홈 디렉토리"나 “환경 변수"라는 개념이 없습니다. 그저 문자열, 숫자, 불리언의 집합일 뿐입니다.
예방 조치
앞으로 이러한 “물결표 함정"을 피하기 위해 다음과 같은 원칙을 세웠습니다.
- 로컬 리소스를 가리키는 설정 파일에서는 항상 절대 경로를 사용합니다.
- 로그를 주의 깊게 확인합니다.
BunInstallFailedError가 발생했다는 사실 자체가 경로가 패키지로 오인되고 있다는 결정적인 힌트였습니다. - 셋업 스크립트 활용. 여러 사용자나 기기에서 이식성이 필요하다면, 셋업 스크립트를 통해 현재 환경에 맞는 절대 경로가 포함된 설정 파일을 생성하도록 합니다.
쉘에서는 아주 유용한 문자 하나가 JSON 설정에서는 전체 시스템을 망가뜨리는 “알 수 없는 문자"가 될 수 있다는 사실을 잊지 말아야겠습니다.
