---
title: "헤드리스 AI CLI의 샌드박스는 \"없음\"이라고 거짓말한다"
description: "AI 코딩 CLI를 헤드리스 모드로 사용할 때 샌드박스나 권한 제한이 에러를 빈 결과로 오인하게 만들어 모델이 잘못된 판단을 내리는 위험을 설명하며, 실패가 실패로 인식되도록 스모크 테스트를 통해 도구의 응답을 반드시 검증해야 함을 강조함"
date: 2026-07-31
updated: 2026-07-31T01:53:05.018Z
tags: [ai-agent, cli, automation, sandbox]
canonical: https://blog.wooncloud.com/posts/headless-ai-cli-sandbox-false-negative
---

![닫힌 문 아래로 새어나오는 빛](/images/posts/headless-ai-cli-sandbox-false-negative/c4007ec7-65e0-4d8b-94dc-e47f5e5ab42c.webp)

AI 코딩 CLI를 다른 에이전트의 하위 도구로 붙이는 구성이 늘고 있다. 무거운 코드베이스 스캔은 값싼 CLI에 헤드리스(`-p`, one-shot)로 던지고, 비싼 모델은 그 요약만 받아 실제 수정을 한다. 컨텍스트 비용을 아끼는 데 효과가 크다.

이때 반사적으로 붙이는 플래그가 `--sandbox` 다. 읽기만 시킬 거니까 툴 권한을 조여두자는, 지극히 상식적인 판단이다.

그 상식이 정확히 반대로 작동했다.

## 종료 코드 0, 완전히 틀린 답

샌드박스가 파일 접근을 막으면 CLI 안의 모델은 "접근이 거부됐다"는 신호를 받지 못한다. 그저 결과가 비어 있을 뿐이다. 그리고 모델은 빈 결과를 이렇게 해석한다.

> `client/` 디렉터리가 존재하지 않거나, 해당 디렉터리 아래에 파일명에 `Preview`가 포함된 파일이 존재하지 않습니다.

실제로는 그 경로에 6개 파일이 멀쩡히 있었다. 크래시도, 에러 코드도, 경고도 없다. 종료 코드는 0이다.

**거짓 음성이 거짓 양성보다 훨씬 늦게 발각되기 때문에** 이게 위험하다. 없는 파일을 있다고 하면 다음 단계에서 바로 깨진다. 반대로 "없습니다"는 그럴듯하게 흘러가고, 그 위에 "그럼 새로 만들자"는 결정이 쌓인다. 이미 있는 구현을 중복으로 짜고, 커버리지 스윕은 취약점을 놓친 채 통과한다.

## 헤드리스는 툴 승인을 통째로 거부한다

같은 CLI에서 두 번째 함정도 나왔다. 헤드리스 모드는 툴 승인 프롬프트를 띄울 수 없으니, 모든 툴 호출을 조용히 거부한다. 설정 파일에 허용 규칙을 아무리 추가해도 그 경로에서는 무시된다.

증상은 빈 출력 한 줄이 전부였다. 로그를 열어보고서야 원인이 보였다.

```text
tool_confirmation_manager.go:183] Print mode: soft-denying tool confirmation "Bash" at step 8
server.go:1886] Tool confirmation ... step 8 (type=Step_RunCommand approved=false)
```

인터랙티브로 같은 명령을 돌리면 잘 된다. 사람이 승인을 눌러주기 때문이다. 헤드리스로 넘어가는 순간에만 조용히 죽는 종류의 실패다.

## 세 가지 규약

**1. 읽기 전용 목적이라도 샌드박스를 켜지 않는다.** 접근 차단을 부재로 오역하는 모델에게 조여진 권한은 안전장치가 아니라 오답 생성기다. 권한을 줄이는 대신 프롬프트로 범위를 제한하고, 결과를 검증한다.

**2. "못 찾았다"는 결론이 아니라 재확인 신호다.** 부재를 근거로 무언가를 결정하기 전에 결정론적 도구(`rg`, `find`)로 교차 검증한다. LLM의 부재 판정은 원래 신뢰도가 낮은데, 샌드박스는 그걸 체계적으로 더 낮춘다.

**3. "계획 모드"를 쓰기 방지책으로 믿지 않는다.** 플래그 이름이 편집을 안 할 것처럼 생겼어도, 실제로 파일 생성을 지시하니 그대로 만들었다. 의도를 유도할 뿐 강제하지 않는다. 진짜 방어선은 프롬프트의 명시적 금지와 실행 후 확인이다.

```bash
# 스캔 결과는 파일로 떨구고, 레포 오염 여부를 매번 확인한다
some-ai-cli -p "코드를 수정하지 마라. <질문>" --print-timeout 5m > /tmp/brief.md
git status --porcelain
```

## 실패가 실패처럼 보이는가

에이전트를 도구로 감쌀 때 검증해야 할 것은 "성공했을 때 잘 도는가"가 아니다. **실패가 실패처럼 보이는가**다.

사람이 쓰는 CLI는 권한 거부를 붉은 에러로 보여준다. 그 CLI를 모델 뒤에 두는 순간, 같은 거부가 자연어 문장 한 줄로 번역되면서 에러라는 사실 자체가 사라진다. 종료 코드 0, 그럴듯한 문장, 완전히 틀린 결론.

새 도구를 파이프라인에 넣기 전에 **일부러 실패시켜 보는 스모크 테스트**를 한 번 돌릴 가치가 여기에 있다. 있는 걸 찾게 해보고, 없는 걸 찾게 해보고, 두 응답이 구별되는지 본다. 구별되지 않으면 그 조합은 쓰면 안 된다.
