Antigravity CLI 헤드리스 모드(agy -p): 출력 형식과 실패 판정

게시 읽는 시간 약 9분 자동화 워크플로

agy -p가 실패를 어떻게 알리는지 보려고 일부러 실패하는 실행을 몇 개 만들어 봤습니다. 셸 명령을 시켜 권한에 막히게 하고, --print-timeout 1s로 시간 초과도 걸어 봤죠. 둘 다 아무 답도 내지 못했는데, 종료 코드는 0이고 status는 SUCCESS였어요.

공식 문서의 CI 예시는 status가 SUCCESS가 아닐 때만 실패로 처리합니다. 그대로 따라 하면 이 두 실패를 성공으로 넘기게 되는 거죠. 그래서 response가 비었는지와 denied_actions가 있는지까지 보는 검사 함수를 만들어 같은 상황에 다시 돌려 봤고, 이 글에 그대로 실었습니다.

기본 사용은 단순합니다. agy -p "질문" --output-format json --print-timeout 10m처럼 실행하면 status·response·usage가 담긴 JSON 한 줄을 받고 끝나요.

Windows PowerShell 5.1이라면 두 가지만 더 챙기면 됩니다. --json-schema는 JSON 문자열 대신 .json 파일 경로로 넘기고, 파이프 입력은 BOM 없는 UTF-8로 맞추는 것.

2026년 10월 1일 Windows 11 · Windows PowerShell 5.1 테스트 환경에서 Antigravity CLI 1.2.14로 직접 실행했고, 공식 헤드리스 모드·권한 문서를 같은 날 다시 봤습니다.

10월 13일에는 1.3.3에서 실패 판정과 검사 함수를 다시 돌려 봤습니다.

설치와 Gemini CLI에서 옮겨 오는 순서는 Gemini CLI 종료 후 Antigravity CLI 설치·설정 옮기기에 있어요.

확인한 버전·환경

  • Antigravity CLI 1.3.3 · Windows 11 · Windows PowerShell 5.1 · 확인일 2026-10-13

agy -p는 답만 표준 출력으로

질문 하나를 보내고 답을 받은 뒤 끝나는 실행입니다. -p 대신 --print나 --prompt를 써도 같아요.

agy -p "In one word, what color is the sky on a clear day?" --print-timeout 120s
Blue

종료 코드는 0이었습니다. 답은 표준 출력으로, 오류·인증 안내·진행 상황·권한 안내는 표준 오류로 나가니까 $answer = agy -p "..."처럼 받으면 변수에 답만 담깁니다.

생각보다 오래 걸린다는 점은 알아 두세요. 같은 질문에서 잰 시간입니다.

잰 것 시간
text 실행, 시작부터 끝까지 9.9초
json 실행, 시작부터 끝까지 8.9초
그 JSON의 duration_seconds 3.7초

나머지 약 5초는 CLI가 뜨고 준비하는 시간으로 짐작합니다. 짧은 질문을 여러 번 나눠 보내면 이 시간이 매번 붙으니, 묶어서 보내거나 뒤에 나오는 stream-json 입력을 쓰는 게 낫습니다.

로그인은 미리 해 둬야 합니다. 헤드리스 문서에 따르면 헤드리스 모드는 저장된 자격 증명을 쓰고, 터미널이 없는 CI에서 로그인 없이 실행하면 멈춰 있지 않고 authentication required 오류로 끝납니다.

출력 형식 고르기

--output-format으로 표준 출력의 모양을 고릅니다.

형식 표준 출력 맞는 경우
text(기본) 답 텍스트만 답만 필요할 때
json 끝날 때 JSON 한 줄 상태·토큰 수도 볼 때
stream-json 단계마다 JSON 한 줄 진행·도구 호출을 볼 때

json으로 받은 실제 출력입니다.

{"conversation_id":"<id>","status":"SUCCESS","response":"Blue\n","duration_seconds":3.6673292,"num_turns":1,"usage":{"input_tokens":12120,"output_tokens":80,"thinking_tokens":79,"cache_read_tokens":0,"total_tokens":12200}}

한 단어 답인데도 입력 토큰이 12,120개예요. CLI가 기본으로 싣는 지시문과 도구 정의가 대부분인 것으로 짐작합니다. 실패하면 error 필드가 붙고, 권한이 막힌 실행에는 뒤에서 볼 denied_actions가 붙었습니다.

stream-json은 init 이벤트로 시작해 step_update가 이어지고 result로 끝납니다. 셸 명령 echo를 실제로 실행시킨 스트림에서 볼 줄만 남겼어요(--dangerously-skip-permissions를 붙인 실행).

{"event":"init","conversation_id":"<id>","init":{"cwd":"<프로젝트 경로>","tools":[…],"permission_mode":"always-proceed"}}
{"event":"step_update","step_update":{…,"state":"ACTIVE","step_type":"tool","tool_name":"run_command",…}}
{"event":"step_update","step_update":{…,"state":"DONE","step_type":"tool","tool_name":"run_command",…,"output":"hello_agy_lab\r\n"}}}
{"event":"step_update","step_update":{…,"step_type":"agent_response","text_delta":"The command printed: …"}}
{"event":"result","result":{"conversation_id":"<id>","status":"SUCCESS","response":"The command printed: …","duration_seconds":5.3720663,…}}

읽을 때는 이 정도만 알면 됩니다.

  • 도구 단계는 시작할 때 ACTIVE, 끝날 때 DONE으로 두 번 찍히고, DONE 쪽 tool_info.output에 명령의 실제 출력이 있습니다. Windows라 줄 끝이 \r\n이에요.
  • 텍스트만 이어 붙이려면 text_delta를 모으고, 최종 답과 누적 토큰은 마지막 result에서 읽습니다.
  • init의 permission_mode는 기본이 request-review이고, --dangerously-skip-permissions를 붙이면 always-proceed입니다.

Claude Code의 claude -p를 써 봤다면 큰 틀은 같다고 느낄 거예요. -p로 한 번 실행하고 text·json·stream-json을 고르는 건 같습니다.

다만 JSON 필드 이름과 실패를 알리는 방식이 달라서, 한쪽용 검사 스크립트를 그대로 옮기면 안 됩니다. Claude Code 쪽은 Claude Code 헤드리스 모드(claude -p) 글에 있어요.

–json-schema는 파일로 넘기기

--json-schema를 주면 답을 스키마에 맞춘 JSON으로 받습니다. 스키마 문자열, .json 파일 경로, string·number·integer·boolean 같은 기본형 이름을 받아요.

공식 예시처럼 JSON 문자열을 작은따옴표로 감싸 넘기면 Windows PowerShell 5.1에서는 이렇게 끝났습니다(종료 코드 1).

Error: invalid --json-schema: schema is not valid JSON: invalid character 't' looking for beginning of object key string

Windows PowerShell 5.1은 외부 프로그램에 인자를 넘길 때 안쪽 큰따옴표를 지워서, agy가 받은 값은 {type:object,...}처럼 따옴표 없는 문자열이 됩니다. “키 자리에 ’t’가 왔다”는 오류가 그 흔적이에요. Claude Code의 mcp add-json도 같은 이유로 이스케이프가 필요했는데, 그 이야기는 Claude Code MCP 서버 연결 글에 있습니다.

제일 덜 헷갈리는 해결은 스키마를 파일로 두는 겁니다. 같은 내용을 semver.schema.json에 저장하고 경로를 넘기자 성공했어요.

$r = agy -p "Parse the semantic version string v2.14.3 into an object with integer fields major, minor, and patch." --output-format json --json-schema .\semver.schema.json | ConvertFrom-Json
$r.structured_output.minor

값은 response가 아니라 structured_output에서 읽으세요. 문서는 response에 같은 내용이 문자열로 들어간다고 하지만, 직접 해 보니 response에는 스키마에 없는 키가 더 붙어 있더라고요.

필드 실제로 담긴 것
structured_output {"major":2,"minor":14,"patch":3}
response 위 세 키 + toolAction, toolSummary

0과 SUCCESS로는 실패를 못 잡는다

자동화에서 가장 중요한 부분입니다. 글 처음에 말한 실패 둘에 정상 답변과 없는 모델 지정까지, 네 가지 상황을 만들어 비교했어요.

상황 종료 코드 status response
정상 답변 0 SUCCESS Blue
셸 명령이 자동 거부됨 0 SUCCESS 빈 문자열
--print-timeout 1s에 걸림 0 SUCCESS 빈 문자열
없는 모델 지정 1 ERROR 빈 문자열

권한이 막혔을 때

셸 명령을 실행하라는 질문을 권한 설정 없이 보내면 response가 빈 채 "denied_actions":[{"action":"command","display_name":"RunCommand"}]가 붙고, 표준 오류에는 이 안내가 남습니다.

jetski: no output produced — a tool required the "command" permission that headless mode cannot prompt for, so it was auto-denied. Add an allow-rule under permissions.allow in settings.json (e.g. command(<target>)). …

헤드리스 모드는 허락을 물을 수 없으니 승인이 필요한 도구는 거부한 채 계속 진행하고, 종료 코드 0으로 끝나며 표준 오류에 안내만 남깁니다. 작업 폴더 안의 파일 읽기·쓰기는 자동 허용이고, 셸 명령은 기본값이 묻기(Ask)라서 거부돼요.

시간 초과에 걸렸을 때

표준 오류에 [agy] print timeout after 1s with turn in progress; returning partial output이 남고, JSON은 "status":"SUCCESS","response":"","duration_seconds":0이었습니다. 문서의 status 표에는 끝까지 못 간 실행을 뜻하는 RUNNING이 있는데, 실제로는 SUCCESS로 보고됐어요.

주의: 문서의 CI 예시는 status가 SUCCESS가 아닐 때만 실패로 처리하므로, 권한 거부와 시간 초과를 그대로 통과시킵니다.

네 가지를 다 보는 검사 함수

그래서 네 가지를 모두 보는 PowerShell 함수를 만들어 같은 상황에 돌려 봤습니다.

function Invoke-Agy($prompt, $timeout = '10m') {
  $raw = agy -p $prompt --output-format json --print-timeout $timeout 2>$null
  $code = $LASTEXITCODE; $r = $raw | ConvertFrom-Json; $problems = @()
  if ($code -ne 0) { $problems += "exit $code" }
  if ($r.status -ne 'SUCCESS') { $problems += "status $($r.status): $($r.error)" }
  if ([string]::IsNullOrWhiteSpace($r.response)) { $problems += 'empty response' }
  if ($r.denied_actions) { $problems += 'denied: ' + (($r.denied_actions | ForEach-Object { $_.action }) -join ',') }
  if ($problems) { "FAIL -> " + ($problems -join '; ') } else { "OK -> " + $r.response.Trim() }
}
ok:      OK -> Blue
deny:    FAIL -> empty response; denied: command
timeout: FAIL -> empty response

빈 response 검사가 권한 거부와 시간 초과를 모두 잡았고, denied_actions가 원인을 알려 줬습니다. --json-schema를 쓰는 실행이라면 response 대신 structured_output이 있는지 보세요. 실패할 때는 스크립트를 0이 아닌 코드로 끝내야 CI가 실패로 인식합니다.

1.3.3으로 올라간 뒤 같은 함수를 다시 돌려도 세 줄이 똑같이 나왔습니다. 권한 거부와 시간 초과는 여전히 종료 코드 0, SUCCESS로 끝나고 표준 오류의 안내 문구도 그대로였어요. 버전이 올라가도 이 검사는 빼면 안 됩니다.

하나 더 겪은 일이 있어요. 함수의 매개변수 이름을 PowerShell 자동 변수와 같은 $args로 지었더니 인자가 통째로 빠졌고, agy가 -p 없이 대화형 화면으로 떠서 10분 넘게 끝나지 않았습니다. 이때는 --print-timeout도 적용되지 않으니, CI에서는 잡 자체에 시간 제한을 따로 걸어 두세요.

플래그 기본값은 자료마다 다르다

--print-timeout 기본값은 자료마다 다릅니다. 문서는 5m, 1.2.14와 1.3.3의 agy --help는 0s(턴이 끝날 때까지 대기)라고 적어요. 그래서 저는 값을 항상 적어 둡니다.

--effort도 비슷합니다. 1.2.14의 agy --help는 low|medium|high|max를, 2026년 10월 1일의 헤드리스 문서는 low·medium·high를 적어요.

1.3.3의 도움말은 여기에 xhigh까지 더해 low|medium|high|xhigh|max를 적습니다. 그런데 기본 모델에 --effort xhigh를 주니 종료 코드 1과 함께 이렇게 거부됐어요.

error: invalid model selection (--model "" --effort "xhigh"): gemini-3.8-flash has no "xhigh" effort (available: low, medium, high)

따로 시험해 보니 max는 어느 모델에서도 거부됐고, 쓸 수 있는 수준은 모델마다 달랐습니다. 규칙은 Antigravity CLI 모델 고르기에 정리했어요.

셸 명령이 필요하면 허용 규칙으로

막힌 도구는 전역 설정 파일 ~/.gemini/antigravity-cli/settings.json의 permissions.allow에 action(target) 모양의 규칙으로 엽니다. 공식 권한 문서의 예시입니다.

{
  "permissions": {
    "allow": ["command(git)", "command(regex:npm run (build|lint|test))", "write_file(src/)"]
  }
}

Windows에서는 두 가지를 더 보세요.

  • PowerShell·명령 프롬프트에서 단어로 깔끔하게 나뉘지 않는 명령은 정확히 일치해야 하므로, 하위 명령까지 허용하려면 command(regex:git .*)처럼 regex:를 씁니다.
  • 거부(deny)·묻기(ask)·허용(allow)이 겹치면 거부 > 묻기 > 허용 순으로 앞쪽이 이깁니다.

--dangerously-skip-permissions를 붙이면 간단하긴 합니다. 하지만 이 플래그는 파일 쓰기와 명령 실행을 전부 승인해요.

앞의 stream-json 예시는 이 플래그로 명령이 실행되는 걸 확인한 것이지만, 신뢰할 수 있는 프롬프트와 환경이 아니면 범위를 좁힌 허용 규칙을 쓰라는 것이 공식 권고입니다. 저도 그쪽을 권합니다.

허용 규칙에도 대가는 있습니다. 이 설정 파일은 사용자 전체에 걸리니까, 한 스크립트를 위해 연 규칙이 다른 모든 실행에도 적용돼요. 그래서 테스트 환경에서도 전역 파일은 바꾸지 않고 문서의 규칙만 옮겼습니다.

다른 CLI들이 권한을 나누는 방식은 세 CLI의 샌드박스·승인 모델 비교에서 볼 수 있어요.

PowerShell에서 stream-json 입력 보내기

--input-format stream-json을 주면 표준 입력으로 프롬프트를 한 줄씩 받아 같은 대화에서 차례로 처리합니다. 프로세스를 한 번만 띄우니까 --continue로 매번 새로 띄우는 것보다 빨라요. --output-format stream-json과 함께 써야 하고, 프롬프트는 -p가 아니라 표준 입력으로만 받습니다.

참고로 기본은 이전 대화에 이어 가지 않습니다. 가장 최근 대화에 이으려면 --continue(-c), 특정 대화에 이으려면 JSON 출력의 conversation_id를 --conversation으로 넘기면 되고, 여러 턴을 한 프로세스로 처리하려면 이 stream-json 입력을 씁니다.

'{"event":"user","message":{"content":"Reply with exactly the word: apple. Nothing else."}}' | agy --input-format stream-json --output-format stream-json

Windows PowerShell 5.1에서 파이프로 넘겨 보니 인코딩 설정에 따라 결과가 갈렸습니다.

인코딩 설정 영어 프롬프트 한글 프롬프트
새 창 기본값(949, us-ascii) 정상 빈 답에 SUCCESS
BOM이 붙는 UTF-8 종료 코드 1 —
BOM 없는 UTF-8 정상 정상(사과)

BOM이 붙으면 result 이벤트에 "error":"failed to decode stream input: invalid character '\\ufeff' looking for beginning of value"가 찍힙니다. PowerShell이 파이프 내용 앞에 BOM을 붙여 보냈고, agy가 첫 줄을 JSON으로 읽지 못한 거예요.

[Console]::InputEncoding과 $OutputEncoding 중 하나만 BOM이 붙는 UTF-8이어도 이랬습니다. 흔히 쓰는 [System.Text.Encoding]::UTF8이 바로 BOM이 붙는 인코딩이라는 점에 주의하세요.

기본값 쪽 실패는 더 조용합니다. $OutputEncoding이 us-ascii면 한글이 ?로 바뀌어 넘어가요.

“다음 단어를 그대로 따라 쓰세요: 사과”라는 한글 프롬프트에 답 대신 셸 명령 시도와 거부가 남았고, 종료 코드 0에 SUCCESS로 끝났습니다. 앞에서 만든 검사 함수가 필요한 이유가 하나 더 생긴 셈이죠.

해결은 세 값을 모두 BOM 없는 UTF-8로 두는 겁니다.

$utf8 = New-Object System.Text.UTF8Encoding $false
[Console]::InputEncoding = $utf8
[Console]::OutputEncoding = $utf8
$OutputEncoding = $utf8

이 설정에서 한글 프롬프트에 사과로 정확히 답했고, -p로 넘긴 한글 질문의 답도 변수에 깨지지 않고 담겼습니다. 콘솔 출력 인코딩이 기본값 949일 때는 같은 답이 ?뚮???처럼 깨진 채 들어갔고요. 이 설정을 PowerShell 프로필에 두는 법은 Windows에서 AI CLI 한글 깨짐 원인과 해결에 정리했습니다.

마치며

agy -p는 쓰기 쉬운 만큼 실패를 조용히 삼킵니다. 권한 거부, 시간 초과, 인코딩 때문에 깨진 한글까지 전부 종료 코드 0과 SUCCESS로 끝났으니까요. 스크립트에 넣는다면 종료 코드 하나만 믿지 말고, 위 Invoke-Agy 같은 검사로 빈 response와 denied_actions까지 보도록 감싸 두시길 권합니다.

MCP 도구를 스크립트에서 쓸 계획이라면 이어서 Antigravity CLI MCP 서버 연결과 설정 파일에서 mcp(서버/*) 허용 규칙을 보세요.

참고한 공식 자료

  • Antigravity CLI — Headless mode — 출력 형식 세 가지와 필드, --json-schema, stream-json 입력, 권한 처리, 종료 코드와 status 값, CI 예시, 플래그 표(확인일 2026-10-01)
  • Antigravity — Agent permissions — action(target) 규칙, 거부·묻기·허용 우선순위, 기본 동작, Windows에서 regex:가 필요한 경우, CLI 전역 설정 파일 위치(확인일 2026-10-01)

변경 이력

  • 2026-10-13 — Antigravity CLI 1.3.3에서 다시 확인해, 권한 거부와 시간 초과가 여전히 종료 코드 0·SUCCESS로 끝나 검사 함수가 그대로 필요하다는 결과와, 도움말에 새로 생긴 --effort xhigh가 Gemini 3.8 Flash에서는 거부된다는 결과를 더했습니다.
  • 2026-10-01 — --effort max를 실제로 시험한 결과(시험한 모델 모두에서 거부)로 자주 묻는 질문의 답을 고쳤습니다.