이 문서는 사용자가 프롬프트를 입력하는 순간부터 최종 응답이 터미널에 렌더링되기까지, 하나의 요청이 Claude Code 내부 파이프라인 전체를 통과하는 경로를 단계별로 추적한다. 각 단계에서 어떤 모듈이 개입하고, 어떤 데이터가 변환되며, 어떤 조건 분기가 발생하는지를 소스 코드에 기반하여 정확하게 서술한다.
---
## 전체 흐름 다이어그램
```mermaid
sequenceDiagram
actor User
participant CLI as CLI (main.tsx)
participant REPL as REPL (screens/REPL.tsx)
participant PUI as processUserInput()
participant CTX as Context Collection (context.ts)
startMdmRawRead() // MDM 설정 읽기 (plutil/reg query 서브프로세스)
startKeychainPrefetch() // macOS Keychain: OAuth 토큰 + 레거시 API 키 병렬 읽기
```
이 세 작업이 먼저 실행된 후 Commander.js가 CLI 옵션을 파싱한다. `--model`, `--tools`, `--add-dir`, `--print`, `--output-format` 등 수십 개의 옵션이 여기서 처리된다. 파싱이 완료되면 최종적으로 `launchRepl()`을 호출하여 인터랙티브 세션을 시작한다.
REPL은 Ink(React 기반 터미널 UI 라이브러리)로 렌더링된 `PromptInput` 컴포넌트를 통해 사용자 입력을 수신한다. 사용자가 Enter를 누르면 `handlePromptSubmit()`이 호출되고, 이것이 `processUserInput()`으로 이어진다.
`processUserInput()`은 원시 입력을 API 호출에 적합한 메시지 배열로 변환하는 게이트웨이다. 내부적으로 `processUserInputBase()`를 호출한 뒤 `UserPromptSubmit` 훅을 실행한다.
**입력 분기 처리:**
| 입력 유형 | 분기 경로 |
|---|---|
| `/` 로 시작하는 문자열 | `processSlashCommand()` — 슬래시 커맨드 처리 |
| `bash` 모드 | `processBashCommand()` — Bash 직접 실행 |
| 이미지 포함 (ContentBlockParam[]) | 이미지 리사이즈 및 base64 처리 후 일반 경로 |
| 일반 텍스트 | `processTextPrompt()` — UserMessage 생성 |
**슬래시 커맨드 탐지:**
입력이 `/`로 시작하고 `skipSlashCommands`가 false이면 슬래시 커맨드로 처리된다. `parseSlashCommand()`로 커맨드 이름을 파싱한 뒤 `findCommand()`로 등록된 커맨드를 탐색한다. 원격 브리지(CCR) 클라이언트에서 온 입력은 `bridgeOrigin` 플래그와 `isBridgeSafeCommand()` 검사를 통해 안전한 커맨드만 허용한다.
`QueryEngine.submitMessage()`는 API 호출 전에 시스템 프롬프트를 구성하기 위해 두 가지 컨텍스트 수집 함수를 호출한다. 두 함수 모두 `lodash-es/memoize`로 메모이제이션되어 동일 대화 내에서는 한 번만 실행된다.
### `getSystemContext()`
Git 저장소 정보를 수집하여 시스템 프롬프트에 포함시킨다. `getGitStatus()`를 내부적으로 호출하며, 다음 git 명령들을 병렬로 실행한다:
```
git --no-optional-locks status --short // 변경 파일 목록
git --no-optional-locks log --oneline -n 5 // 최근 커밋 5개
git config user.name // 사용자 이름
getBranch() // 현재 브랜치
getDefaultBranch() // 기본 브랜치 (PR 대상)
```
반환되는 컨텍스트 문자열 예시:
```
Current branch: main
Main branch (you will usually use this for PRs): main
Git user: Jane Smith
Status:
M src/query.ts
Recent commits:
abc1234 Fix tool permission check
...
```
상태 문자열이 2,000자를 초과하면 잘라낸 뒤 BashTool 사용을 안내하는 메시지를 덧붙인다. 원격 CCR 환경(`CLAUDE_CODE_REMOTE` 환경변수) 또는 git 지시사항이 비활성화된 경우 이 단계를 건너뛴다.
### `getUserContext()`
사용자 정의 지시사항과 현재 날짜를 수집한다:
- **CLAUDE.md 탐색**: 작업 디렉터리에서 상위 방향으로 `CLAUDE.md` 파일을 탐색(`getMemoryFiles()` → `filterInjectedMemoryFiles()` → `getClaudeMds()`). `CLAUDE_CODE_DISABLE_CLAUDE_MDS` 환경변수 또는 `--bare` 모드에서 비활성화.
- **현재 날짜**: `getLocalISODate()`로 ISO 형식 날짜 생성 후 `"Today's date is YYYY-MM-DD."` 형태로 포함.
수집된 컨텍스트는 시스템 프롬프트의 일부로 `prependUserContext()` / `appendSystemContext()`를 통해 API 요청에 포함된다.
---
## 단계 4: LLM 호출 (QueryEngine → query())
**관련 파일:** `src/QueryEngine.ts`, `src/query.ts`
### QueryEngineConfig
`QueryEngine`은 하나의 대화 세션에 대응하는 클래스다. 생성자에서 받는 `QueryEngineConfig`의 핵심 필드:
```typescript
{
cwd: string, // 현재 작업 디렉터리
tools: Tools, // 사용 가능한 도구 목록
commands: Command[], // 슬래시 커맨드 목록
mcpClients: MCPServerConnection[], // MCP 서버 연결
agents: AgentDefinition[], // 에이전트 정의
canUseTool: CanUseToolFn, // 도구 권한 확인 함수
getAppState: () => AppState,
setAppState: (f) => void,
maxTurns?: number, // 최대 에이전트 루프 횟수
maxBudgetUsd?: number, // 예산 제한
}
```
### submitMessage() → query() 흐름
`submitMessage()`는 다음 순서로 동작한다:
1.`processUserInput()`으로 사용자 입력을 메시지 배열로 변환
2.`fetchSystemPromptParts()`로 시스템 프롬프트 조립 (기본 프롬프트 + 사용자 컨텍스트 + 시스템 컨텍스트)
3. 세션 트랜스크립트 기록 (`recordTranscript()`) — API 응답 전에 선제적으로 저장