> ## Documentation Index
> Fetch the complete documentation index at: https://docs.befailproof.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 문제 해결

> AgentEye 문제 해결 문서입니다.

이 가이드는 프로덕션 환경에서 자주 발생하는 증상을 구체적인 진단 방법 및 해결책과 연결하여, 별도의 모니터링 인프라 없이 기존 도구만으로 인시던트를 해결할 수 있도록 합니다. 서버, 수집기, 대시보드, AI 어시스턴트, Python SDK, 헬스/인증서 모니터링, 백업, ClickHouse 기반 분석, 멀티테넌시를 다룹니다.

대시보드 페이지는 `/<org-slug>/…` 형태의 조직 범위 경로를 사용하며, 이벤트 스트림은 조직 홈(`/<org-slug>/`)입니다. 이 가이드에서 언급하는 페이지 이름(예: `/sessions`, `/queries`)은 해당 조직 범위 경로를 가리킵니다.

***

## 로그 확인

AgentEye는 별도의 로깅 또는 모니터링 스택을 포함하지 않습니다. 서버와 대시보드 모두 구조화된 로그를 **stdout**에 출력하므로, 별도의 로그 수집기 없이 `kubectl` 또는 `docker`로 직접 확인할 수 있습니다.

### Kubernetes

서버 및 대시보드의 실시간 로그 확인:

```bash theme={null}
kubectl logs -n agenteye -l app=server    -f --timestamps
kubectl logs -n agenteye -l app=dashboard -f --timestamps
```

유용한 변형 명령어:

| 목적                     | 명령어                                                               |
| ---------------------- | ----------------------------------------------------------------- |
| 마지막 200줄 (실시간 아님)      | `kubectl logs -n agenteye -l app=server --tail=200 --timestamps`  |
| 이전 크래시 로그              | `kubectl logs -n agenteye <pod-name> --previous`                  |
| 모든 레플리카 동시 tail        | `kubectl logs -n agenteye -l app=server --max-log-requests=10 -f` |
| Postgres (StatefulSet) | `kubectl logs -n agenteye postgres-0 -f`                          |

### Docker Compose

```bash theme={null}
docker logs -f agenteye-server
docker logs -f agenteye-dashboard
```

### 대시보드와 서버 간 단일 요청 추적

모든 대시보드 요청에는 `request_id`가 태그되며, `x-request-id` 헤더를 통해 서버로 전달됩니다. 서버는 해당 요청에 대한 응답 헤더와 모든 로그 줄에 이 ID를 포함합니다. 단일 요청을 처음부터 끝까지 추적하려면:

1. 응답 헤더에서 ID를 캡처합니다:
   ```bash theme={null}
   curl -i https://dashboard.example.com/api/events | grep -i x-request-id
   # x-request-id: 9a7b0d6e-5e9b-4c0a-9f8a-5f1e4b5c0f3a
   ```
2. 두 파드의 로그에서 해당 ID를 검색합니다:
   ```bash theme={null}
   REQ=9a7b0d6e-5e9b-4c0a-9f8a-5f1e4b5c0f3a
   kubectl logs -n agenteye -l app=dashboard --tail=5000 | grep "$REQ"
   kubectl logs -n agenteye -l app=server    --tail=5000 | grep "$REQ"
   ```

동일한 `request_id`를 공유하는 대시보드의 `proxy passthrough`, `withAuth: authorized`, `upstream response` 로그 줄과 서버의 `http request received` / `http request completed` 쌍을 함께 확인할 수 있습니다.

### JSON 로그와 `jq`

대시보드에서 `AE_LOG_JSON=1`을 설정하면(기본값: `NODE_ENV=production`일 때 활성화) 한 줄에 하나의 JSON 객체를 출력합니다. 구조적으로 필터링하려면:

```bash theme={null}
kubectl logs -n agenteye -l app=dashboard --tail=5000 \
  | jq 'select(.level == "warn" or .level == "error")'

kubectl logs -n agenteye -l app=dashboard --tail=5000 \
  | jq 'select(.route == "POST /api/keys")'
```

Rust 서버는 `key=value` 형식의 tracing 로그를 출력하므로 `jq` 없이도 grep이 잘 됩니다:

```bash theme={null}
kubectl logs -n agenteye -l app=server --tail=5000 | grep 'status=5'   # 5xx
kubectl logs -n agenteye -l app=server --tail=5000 | grep 'actor_key_id='
```

### 로그 상세도 높이기

| 컴포넌트 | 환경 변수          | 예시                                                        |
| ---- | -------------- | --------------------------------------------------------- |
| 서버   | `RUST_LOG`     | `RUST_LOG=debug` 또는 `RUST_LOG=agenteye_server=debug,info` |
| 대시보드 | `AE_LOG_LEVEL` | `AE_LOG_LEVEL=debug`                                      |

서버에서 `debug`를 설정하면 인증마다 `api key authenticated` 줄이 추가됩니다. 대시보드에서 `debug`를 설정하면 `upstream request`, `session validated`, `proxy passthrough` 줄이 추가됩니다.

### 로그 보존

컨테이너 stdout은 휘발성이며, kubelet이 로그 파일을 순환(기본값: 컨테이너당 약 10MiB)하고 디스크에 소수의 파일만 유지합니다. 파드가 삭제되면 로그도 사라집니다. 더 긴 보존 기간이나 파드 간 검색이 필요한 경우, `/var/log/containers/`를 tail하는 로그 수집기(Loki, CloudWatch, Cloud Logging, Datadog 등)를 클러스터에 연결하세요. AgentEye는 특정 수집기를 요구하거나 권장하지 않습니다.

***

## 인증 문제

### `docker pull`이 "unauthorized"로 실패하는 경우

`AGENTEYE_TOKEN`으로 GHCR에 Docker 인증이 되어 있는지 확인하세요:

```bash theme={null}
echo $AGENTEYE_TOKEN | docker login ghcr.io -u x --password-stdin
```

토큰은 `agenteye-enterprise` org의 `read:packages` 권한이 있어야 합니다. 토큰이 작동하지 않으면 `support@exosphere.host`에 문의하세요.

### `gh release download`가 404 또는 401을 반환하는 경우

* 셸에서 `AGENTEYE_TOKEN`이 export되어 있는지 확인합니다: `echo $AGENTEYE_TOKEN`
* `GITHUB_TOKEN=$AGENTEYE_TOKEN gh release download ...` 형식을 사용하고 있는지 확인합니다(`gh` CLI는 `GITHUB_TOKEN`을 읽습니다)
* 토큰에 `agenteye-enterprise/releases`의 `contents:read` 권한이 필요합니다

***

## 서버 문제

### 서버가 "invalid port number"로 실패하는 경우

`POSTGRES_PASSWORD`(또는 다른 자격 증명)에 URL 특수 문자(`/`, `+`, `=`)가 포함되어 `DATABASE_URL` 파싱이 실패합니다. 16진수 인코딩을 사용하여 비밀번호를 재생성하세요:

```bash theme={null}
NEW_PASS=$(openssl rand -hex 24)
```

그런 다음 Kubernetes 시크릿과 Postgres 내부의 비밀번호를 업데이트(또는 Docker Compose용 `.env`를 재생성)하고 서버를 재시작하세요. 전체 절차는 [enterprise-docs/kubernetes-deployment.md](/ko/agenteye/kubernetes-deployment) § "PostgreSQL credentials"를 참조하세요.

### 서버가 시작 즉시 종료되는 경우

컨테이너 로그를 확인합니다:

```bash theme={null}
docker logs agenteye-server
```

주요 원인:

* `DATABASE_URL`이 설정되지 않았거나 잘못된 형식: 서버가 오류를 로그에 기록하고 종료합니다.
* Postgres에 연결할 수 없음: Postgres 컨테이너 또는 관리형 DB가 실행 중이고 호스트/포트가 올바른지 확인합니다.
* 마이그레이션 실패: SQL 오류가 있는지 로그를 확인합니다.

### `GET /health`가 non-200을 반환하거나 타임아웃되는 경우

첫 시작 시 서버가 마이그레이션을 실행 중일 수 있습니다. 잠시 기다렸다가 재시도하세요:

```bash theme={null}
curl http://localhost:8080/health
```

문제가 지속되면 `docker logs agenteye-server`에서 오류를 확인하세요.

### `GET /ready`가 503을 반환하는 경우

`/ready`는 준비 상태 프로브로, 서버가 **Postgres 또는 ClickHouse**에 연결할 수 없을 때 `503`을 반환합니다. 응답 본문에 실패한 의존성이 명시됩니다:

```bash theme={null}
curl -s http://localhost:8080/ready
# {"status":"not_ready","checks":{"postgres":"ok","clickhouse":"down","redis":"ok"}}
```

`down`으로 보고된 의존성을 수정하세요: ClickHouse/Postgres 파드가 `Running` 상태인가요? `CLICKHOUSE_URL` / `DATABASE_URL`이 올바르고 접근 가능한가요? Kubernetes에서 파드는 `/ready`가 회복될 때까지 `NotReady`로 표시됩니다. 이는 예상된 동작이며 헬스 모니터링이 경고하는 신호입니다. Redis는 원인이 되지 않습니다: 보고되지만 준비 상태에 영향을 미치지 않습니다.

### 수집기가 401 Unauthorized를 반환하는 경우

수집기의 API 키에 `events:add` 권한이 없거나 키가 비활성화되어 있습니다. 올바른 권한으로 새 키를 생성하세요:

```bash theme={null}
curl -s -X POST http://your-server:8080/keys \
  -H "Authorization: Bearer $ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"new-collector","permissions":["events:add"]}'
```

### 인증된 요청이 갑자기 느려진 경우(\~5ms 대신 \~200ms)

Redis가 다운되었으나 `REDIS_URL`은 설정된 상태일 때 나타나는 증상입니다. 모든 캐시 호출이 100ms 후 타임아웃되고 Postgres로 폴스루됩니다. 인증 및 OTP 경로에서는 요청마다 두 번의 폴스루가 발생합니다.

서버 로그에서 확인:

```
auth cache: L2 get failed error=redis call timed out
```

해결 방법:

1. `redis-cli -h <your-redis> ping`으로 클러스터 네트워크에서 Redis에 접근 가능한지 확인합니다.
2. Redis가 잠깐 다운되었다가 복구된 경우, **서버 파드를 재시작**하세요. `redis::aio::ConnectionManager`는 기본 연결이 끊긴 후 안정적으로 재연결하지 못합니다. 파드 재시작 시 새 연결을 깔끔하게 가져옵니다. 대시보드도 마찬가지입니다.
3. 당장 Redis를 운영하지 않으려면 배포에서 `REDIS_URL`을 해제하고 재시작하세요. 두 서비스 모두 캐시 없이 실행됩니다(정확성은 유지되며, 지연 시간은 Redis 이전 기준으로 돌아갑니다).

### 서버 로그에 `OTP request rate-limited`가 기록되지만 사용자는 한 번만 시도했다고 하는 경우

Redis가 연결 불가 상태였는지 확인하세요. 폴백 경로는 `SELECT COUNT(*) FROM otp_codes WHERE created_at > now() - interval '15 minutes'`를 사용하는데, 이전에 생성된 OTP 행이 포함됩니다. 사용자가 한 시간 동안 "재전송"을 반복 클릭했다면 15분 창 내에 여전히 5개 이상의 코드가 있을 수 있습니다. 창이 롤오버될 때까지 기다리거나, `DELETE FROM otp_codes WHERE user_id = $1 AND created_at > now() - interval '15 minutes'`(운영자 콘솔)를 실행하여 해결하세요.

### `ALLOWED_EMAILS` / `SESSION_TTL_SECS` / `OTP_TTL_SECS`를 변경하고 재시작했지만 변경이 적용되지 않는 경우

이 환경 변수들은 **최초 부팅 시에만 적용되는 시드 값**입니다. `settings` 테이블에 해당 키의 행이 존재하면, 그 행이 진실의 원천이 됩니다. 환경 변수는 최초 부팅 시 한 번만 읽히고 이후 재시작 시에는 무시됩니다.

최초 부팅 이후 변경하려면, 대시보드에 로그인하여 `/settings`에서 편집하세요. 변경 사항은 모든 레플리카에 수초 내에 적용되며 재시작이 필요 없습니다.

환경 변수에서 강제로 재시드해야 하는 경우(드물며 주로 개발 환경에서만 유용), `DELETE FROM settings WHERE key = '<key>'` 후 서버를 재시작하면 다음 부팅 시 현재 환경 변수 값을 가져옵니다. 프로덕션에서는 `/settings`를 통한 편집이 지원되는 방식입니다.

***

## 수집기 문제

### 수집기는 시작되지만 이벤트가 대시보드에 나타나지 않는 경우

1. 수집기가 실행 중인지 확인합니다: `systemctl status agenteye-collector`(Linux) 또는 프로세스를 확인합니다.
2. `AGENTEYE_URL`이 `http(s)://your-server-host:8080/events`(`/events` 경로 포함)를 가리키는지 확인합니다.
3. 즉각적인 출력을 보려면 단발성 플러시를 실행합니다:
   ```bash theme={null}
   agenteye-collector flush
   ```
4. Python SDK가 실제로 파일을 기록하고 있는지 확인합니다: `ls ${AGENTEYE_HOME:-~/.agenteye}/events/`
5. `${AGENTEYE_HOME:-~/.agenteye}/failed/`에 파일이 있다면 업로드가 실패하고 있는 것입니다. 수집기 로그에서 오류를 확인하세요. 4xx 오류(잘못된 키 또는 URL)나 네트워크 문제일 가능성이 높습니다.

### `$AGENTEYE_HOME/events/`에 파일이 쌓이고 업로드되지 않는 경우

* 수집기가 실행 중이 아닐 수 있습니다. 시작하세요: `agenteye-collector start`. 시작 시 기존 이벤트를 자동으로 플러시합니다.
* 수집기 헬스 확인: `agenteye-collector health`
* 수집기는 실행 중이지만 서버에 연결할 수 없을 수 있습니다. 수집기와 서버 호스트 간 방화벽 규칙을 확인하세요.

### `$AGENTEYE_HOME/failed/`에 파일이 있는 경우

파일은 모든 재시도가 소진된 후(기본값: 지수 백오프를 적용한 5회) `failed/`로 이동됩니다. 이는 다음 중 하나를 의미합니다:

* 서버가 4xx 오류를 반환(잘못된 키, 잘못된 URL, 또는 페이로드 문제)
* 전체 재시도 창 동안 서버에 연결할 수 없음

근본 원인을 수정한 후 수동으로 재대기열에 추가하세요:

```bash theme={null}
mv ${AGENTEYE_HOME:-~/.agenteye}/failed/*.jsonl ${AGENTEYE_HOME:-~/.agenteye}/events/
agenteye-collector flush
```

### 수집기가 모든 업로드에서 `network error`를 보고하는 경우(TLS 핸드셰이크 실패)

`curl -k`로 `AGENTEYE_URL`에 접근하면 성공하지만 수집기 바이너리가 매번 `error sending request for url (...)`로 실패한다면, AgentEye 서버가 공개 신뢰 CA에서 서명되지 않은 TLS 인증서를 제공하고 있는 것입니다.

**프로덕션 경로**는 `deploy/base/certificates/domain.env`에 구성된 ACME 수집 호스트명입니다([`kubernetes-deployment.md`](/ko/agenteye/kubernetes-deployment) Phase 3.1 / 4.2 참조). `INGEST_DOMAIN`이 공개 Traefik LB로 해석되고 cert-manager가 Let's Encrypt 인증서를 발급하면, 수집기는 **`AGENTEYE_TLS_CA` 없이** 시스템 신뢰 저장소에 대해 서버 인증서를 검증합니다. 이전에 자체 서명 배포 시 설정했다면 이를 해제하세요.

**증상: 수집기가 어제까지 작동했는데 약 90일 후 오늘 실패하는 경우.** 배포가 여전히 `ingest-tls`에 레거시 `selfsigned` 발급자를 사용하고 있다는 의미입니다. 90일짜리 인증서가 순환되어 고정된 CA 파일이 오래된 것이 됐습니다. 클러스터를 ACME 발급자로 전환하여 영구적으로 수정하세요(배포 가이드 Phase 3.1). 단기 해결책으로, 현재 서버 인증서를 재추출하고 `AGENTEYE_TLS_CA`를 업데이트하세요:

```bash theme={null}
kubectl get secret ingest-tls-cert -n agenteye \
  -o jsonpath='{.data.tls\.crt}' | base64 -d > /etc/agenteye/server-ca.crt
```

```bash theme={null}
export AGENTEYE_TLS_CA=/etc/agenteye/server-ca.crt
agenteye-collector flush
```

`AGENTEYE_TLS_CA`는 추가 신뢰 앵커를 추가합니다. 표준 공개 루트는 계속 신뢰됩니다.

### 배포 후 `ingest-tls` 인증서가 `Ready: False` 상태에서 멈춰 있는 경우

```bash theme={null}
kubectl describe certificate ingest-tls -n agenteye
```

`Events`와 참조된 `Order` / `Challenge`를 확인하세요. 주요 원인:

* **DNS가 공개 LB로 해석되지 않음.** HTTP-01 검증자가 `INGEST_DOMAIN`에 도달하지 못합니다. `dig +short INGEST_DOMAIN`으로 확인하세요. `traefik-public` LoadBalancer의 `EXTERNAL-IP`와 동일한 주소로 해석되어야 합니다. DNS가 전파되면 cert-manager가 자동으로 재시도하므로 Certificate를 삭제할 필요가 없습니다.
* **로드 밸런서 / 보안 그룹에서 포트 80이 차단됨.** HTTP-01은 Let's Encrypt의 공개 검증자가 포트 80에 접근 가능해야 합니다. 업스트림 WAF나 SG가 `:80`을 제한하고 있다면 열어주세요(Traefik 설정은 HTTPS로 리디렉션하지만, Boulder는 리디렉션을 따르고 응답을 수락합니다).
* **`dnsNames`가 치환되지 않음.** `kubectl get certificate ingest-tls -n agenteye -o jsonpath='{.spec.dnsNames}'`가 `INGEST_DOMAIN_PLACEHOLDER`를 표시한다면 `domain.env` 단계를 건너뛴 것입니다. `domain.env.example`에서 생성하고 재적용하세요.
* **Let's Encrypt 속도 제한.** 동일 호스트명에 대한 반복 실패한 주문은 중복 인증서 또는 유효성 검사 실패 한도를 초과합니다. 재시도 전 최소 한 시간 기다리세요. 정확한 속도 제한 메시지는 Order 상태를 확인하세요.

### `dashboard-tls` 인증서가 `Ready: False` 상태이거나 브라우저에서 여전히 경고가 표시되는 경우

위의 `ingest-tls`와 동일한 진단 절차를 따르세요(`kubectl describe certificate dashboard-tls -n agenteye`). DNS, 포트 80, 플레이스홀더, 속도 제한 원인이 모두 적용되며, 대시보드에만 해당하는 두 가지 추가 원인이 있습니다:

* **`DASHBOARD_DOMAIN`이 잘못된 LoadBalancer를 가리킴.** 공개 수집용 Traefik LB가 아닌 *대시보드* Traefik LB를 가리켜야 합니다. 호스트명을 `dig +short`로 확인하고 대시보드 LB 주소와 비교하세요.
* **대시보드 Traefik 인스턴스가 챌린지를 처리하지 못함.** cert-manager의 HTTP-01 솔버를 위한 범위 지정 Ingress 공급자를 활성화하는 번들 대시보드 값 파일과 함께 설치되어야 합니다. 없으면 솔버가 라우팅 불가능하여 Order가 영구적으로 `pending` 상태가 됩니다. 제공된 값으로 인스턴스를 업그레이드하면 대기 중인 챌린지가 자동으로 완료됩니다.
* **LoadBalancer에 IP 제한이 걸려 있음.** 소스 범위는 포트 80에도 적용되어 Let's Encrypt의 검증자를 차단합니다. 최초 발급과 약 75일마다의 갱신 시 모두 영향을 받습니다. LB를 다시 열거나, 잠그기 전에 지원팀과 DNS-01 솔버를 조율하세요.

발급이 실패하는 동안 대시보드는 이전 인증서(또는 신규 설치의 경우 ingress 기본값)로 계속 서비스됩니다. 브라우저 경고로 접근이 불편해질 뿐, 서비스가 중단되지는 않습니다.

### 대시보드에 신뢰할 수 있는 인증서가 적용된 후에도 CLI가 TLS 검증을 건너뛰는 경우

`--insecure`는 로그인 시 `cli.json`에 저장됩니다. 대시보드가 공개 신뢰 인증서를 제공하면, `agenteye --base-url https://<your-dashboard-domain> --secure login`으로 다시 로그인하세요. 검증이 다시 활성화되어 저장되고 시작 경고가 사라집니다.

***

## 대시보드 문제

### `ADMIN_EMAIL` 사용자를 비활성화하거나 편집할 수 없는 경우

이는 의도된 동작입니다. `ADMIN_EMAIL`과 일치하는 사용자는 서버 시작 시마다 보호됨으로 표시됩니다. 대시보드는 해당 행의 비활성화 버튼을 숨기며, API는 해당 사용자에 대한 `DELETE /users/:id` 및 `PUT /users/:id` 요청을 `403 Forbidden`으로 거부합니다. 데이터베이스 트리거도 보호된 행을 비활성화하는 직접 `UPDATE` 문을 거부합니다.

부트스트랩 관리자를 교체하려면, 환경에서 `ADMIN_EMAIL`을 변경하고 서버를 재시작하세요. 새 이메일이 보호됨으로 업서트됩니다. 이전 관리자는 데이터베이스에서 명시적으로 제거할 때까지 보호 플래그를 유지합니다(이전 이메일이 여전히 유효한 관리자이므로 일반적으로 무방합니다).

### 대시보드에 이벤트가 표시되지 않는 경우

1. 대시보드 환경 변수(`AGENTEYE_SERVER_URL`, `AGENTEYE_API_KEY`)에서 서버 URL과 API 키가 올바른지 확인합니다.
2. 대시보드 API 키에 `events:read` 권한이 필요합니다.
3. 이벤트가 실제로 수집되었는지 확인합니다: `curl http://your-server:8080/events -H "Authorization: Bearer $ADMIN_KEY"`

### `/errors`는 비어 있는데 `/events`에는 빨간색 행이 표시되는 경우

최신 SDK 버전은 실패를 전용 `event_type: "error"` 행 대신 페이로드에 `outcome: "error"`가 포함된 `agent_end` / `tool_result` / `hook_completed` 이벤트로 보냅니다. `/errors` 페이지는 이제 두 가지 모두를 매칭합니다: `/events` 스트림에서 빨간색으로 표시되는 모든 행(명시적 `event_type='error'`, 페이로드의 `outcome`/`status`가 실패 집합에 속하는 경우, `is_error: true`, 또는 truthy `error` 필드)이 `/errors`에 표시됩니다. 이전에 `/events`에 빨간색 행이 있었는데 "이 창에 오류 없음"이 표시됐다면, 대시보드와 서버를 함께 업그레이드하세요(`GET /events`의 확장된 필터 `errored=true`). 그러면 두 뷰가 일치하게 됩니다.

### 넓은 시간 범위에서 `/models`, `/tools`, `/hooks`가 느리거나 로드 실패하는 경우

**증상:** 대규모 이벤트 테이블(수백만 행)에서 `/models`, `/tools`, `/hooks`를 열거나 시간 범위를 `7d`, `30d`, `all`로 넓히면 차트가 로딩되다가 오류가 표시됩니다. 서버 로그에 `latency_aggregate` 요청에 대한 ClickHouse `MEMORY_LIMIT_EXCEEDED`(Code 241) 또는 쿼리 타임아웃이 기록됩니다.

**원인:** 이전 빌드에서는 이 페이지들의 지연 시간 및 분포 롤업을 전체 원시 이벤트 `payload`를 읽고 인메모리 정렬-조인으로 요청/응답 이벤트를 쌍으로 맞추는 쿼리로 계산했습니다. 이로 인해 쿼리 최대 메모리가 창의 크기에 비례하여 증가했고, 바쁜 테넌트에서는 ClickHouse의 쿼리당 메모리 한계를 초과할 수 있었습니다.

**해결 방법:** 이 수정을 포함하는 빌드로 업그레이드하세요. 롤업은 이제 압축된 승격 컬럼만 읽고 스트리밍 집계로 이벤트를 쌍으로 맞추므로, 최대 메모리가 더 이상 원시 페이로드와 함께 확장되지 않습니다. 넓은 창도 메모리 한계 내에서 빠르게 반환됩니다. 개선은 전적으로 쿼리 측에서 이루어집니다. 기존 데이터에 즉시 적용되며, 재수집이나 백필이 필요 없습니다.

### 대시보드가 로드되지 않거나 빈 페이지가 표시되는 경우

대시보드 컨테이너 로그를 확인합니다:

```bash theme={null}
docker logs agenteye-dashboard
```

가장 흔한 원인은 `AGENTEYE_SERVER_URL` 또는 `AGENTEYE_API_KEY`가 누락되거나 연결할 수 없는 서버를 가리키는 경우입니다.

### 대시보드 분석 / 텔레메트리

대시보드는 기본적으로 익명의 제품 사용 분석을 PostHog에 전송하며, 대시보드 자체의 `/ingest` 경로(퍼스트 파티 `https://us.i.posthog.com` 리버스 프록시)를 통해 라우팅됩니다. 브라우저 광고 차단기가 차단하지 않도록 퍼스트 파티로 전송됩니다. 이는 대시보드의 핵심 기능과 독립적입니다:

* **대시보드 컨테이너**(브라우저가 아님)가 PostHog에 연결합니다. `https://us.i.posthog.com`에 대한 아웃바운드 접근이 차단되면 텔레메트리는 무음으로 비활성화되며, 대시보드는 정상적으로 작동하고 사용자에게 오류가 표시되지 않습니다.
* 에이전트, 세션, 이벤트 데이터는 포함되지 않으며 대시보드 UI 사용 데이터만 전송됩니다.
* 텔레메트리를 완전히 비활성화하려면 대시보드 컨테이너에 `AE_ANALYTICS_DISABLED=1`을 설정하고 재시작하세요. 배포 가이드의 [Telemetry & privacy](/ko/agenteye/deployment#telemetry--privacy)를 참조하세요.

### CLI 분석 / 텔레메트리

`agenteye` CLI는 기본적으로 익명의 사용 분석(실행된 명령어, 성공/종료 상태, 실행 시간)을 PostHog에 전송합니다. 이는 CLI 기능과 독립적입니다:

* **CLI를 실행하는 머신**이 `https://us.i.posthog.com`에 직접 연결합니다. 아웃바운드 접근이 차단되면 텔레메트리는 무음으로 비활성화되며(전송에 시간 제한이 있어 명령어 실행이 지연되지 않습니다) CLI는 정상적으로 작동합니다.
* 에이전트, 세션, 이벤트 데이터는 포함되지 않습니다. 명령어 **인수 및 플래그 값**(대시보드 URL, 토큰, 이메일, 세션 ID, 쿼리 필터)은 전송되지 않습니다.
* 비활성화하려면 CLI 환경에서 `AGENTEYE_ANALYTICS_DISABLED=1`(또는 크로스 도구 `DO_NOT_TRACK=1`)을 설정하세요. CLI 가이드의 [Telemetry & privacy](/ko/agenteye/cli#telemetry--privacy)를 참조하세요.

***

## AI 어시스턴트 문제

전체 설정은 [enterprise-docs/assistant.md](/ko/agenteye/assistant)를 참조하세요.

### 어시스턴트 버블이 표시되지 않는 경우

다음 조건이 **모두** 충족될 때만 버블이 표시됩니다:

* 로그인한 사용자에게 `agent:use` 권한이 있어야 합니다.
* 대시보드에 `AGENTEYE_AGENT_URL`이 설정되어 있고 `agent` 서비스에 접근 가능해야 합니다.
* `agent` 서비스에 LLM 엔드포인트가 구성되어 있어야 합니다(`ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`을 통한 게이트웨이, 또는 Bedrock/Vertex). 아무것도 설정되지 않으면 에이전트가 "구성되지 않음"을 보고하고 버블이 숨겨집니다.

대시보드 호스트에서 에이전트 헬스를 확인하세요: `curl http://agent:9100/health`는 `{"status":"ok","llm_configured":true,...}`를 반환해야 합니다.

### 어시스턴트가 특정 데이터를 읽을 수 없다고 하는 경우

도구는 사용자별로 제한됩니다. 사용자에게 `evaluations:read`(또는 `events:read`, `dashboards:read`) 권한이 없으면 해당 도구가 제공되지 않으며, 어시스턴트는 해당 데이터를 읽을 수 없다고 말합니다. 관련 읽기 권한을 부여하세요.

### 메시지 전송 시 "assistant not configured" (HTTP 503)가 발생하는 경우

`agent` 컨테이너에 LLM 엔드포인트가 구성되지 않았거나, 대시보드의 `AGENTEYE_AGENT_TOKEN`이 에이전트의 토큰과 일치하지 않습니다. 두 값을 모두 설정하고 재시작하세요.

### `agent` 컨테이너가 부하 시 재시작되거나 OOM이 발생하는 경우

각 대화는 단기적인 자식 프로세스를 생성합니다. 컨테이너가 init 프로세스로 실행되는지 확인하세요(이미지가 `tini`를 사용하며, Compose에서는 `init: true` 설정). 적절한 메모리 한도를 설정하고, 필요시 `AGENTEYE_AGENT_MAX_STEPS`를 줄이세요.

***

## CLI 문제

### `agenteye`가 `ModuleNotFoundError: No module named 'click'`으로 시작 실패하는 경우

**0.1.6** 버전의 `agenteye` CLI를 새로 설치하면 시작 시 다음 오류가 발생할 수 있습니다:

```
ModuleNotFoundError: No module named 'click'
```

0.1.6은 `typer`가 `click`을 간접적으로 설치하는 것에 의존했습니다. 현재 `typer` 릴리스는 더 이상 이를 포함하지 않아, 깨끗한 환경에서 패키지가 누락됩니다. `click`에 직접 의존하는 **0.1.7 이상으로 업그레이드**하세요:

```bash theme={null}
pipx upgrade agenteye      # pipx로 설치한 경우 (또는: pipx install --force agenteye)
uv tool upgrade agenteye   # uv로 설치한 경우
pip install --upgrade agenteye
```

설치 안내는 [enterprise-docs/cli.md](/ko/agenteye/cli)를 참조하세요.

***

## Python SDK 문제

### `$AGENTEYE_HOME/events/`에 파일이 생성되지 않는 경우

SDK는 이벤트를 버퍼링하고 기본적으로 500ms마다 플러시합니다. 플러시 전에 프로세스가 종료되면 이벤트가 손실될 수 있습니다. 단기 실행 스크립트에서는 `agenteye.configure(flush_interval=0.1)`로 더 빠른 플러시를 설정하거나, 플러시 사이클이 완료될 때까지 프로세스가 실행되도록 하세요.

`AGENTEYE_HOME`이 설정되어 있다면, SDK가 `~/.agenteye/events/`가 아닌 `$AGENTEYE_HOME/events/`에 기록하는지 확인하세요(SDK ≥ 0.0.1b5 필요).

### `ValueError: Reserved field names cannot be used as custom fields`

`timestamp`, `type`, `environment`는 예약된 이름으로 커스텀 필드로 사용할 수 없습니다. 이 중 하나를 전달하면 다음 오류가 발생합니다:

```
ValueError: Reserved field names cannot be used as custom fields: [...]
```

문제가 되는 커스텀 필드의 이름을 변경하세요. `session_id`와 `agent_id`는 이벤트 호출의 명시적 파라미터이며 커스텀 필드가 아닙니다. 이를 다시 커스텀 필드로 전달하면 `TypeError`가 발생합니다.

***

## 헬스 모니터링 문제

### Slack으로 경고가 오지 않는 경우(Robusta)

Robusta 헬스 경고는 **옵트인** 방식입니다. 설치하고 Slack 채널을 지정할 때까지 아무것도 전송하지 않습니다. 릴리스와 싱크를 확인하세요:

```bash theme={null}
kubectl get pods -n robusta          # robusta-runner + robusta-forwarder가 Running 상태여야 합니다
kubectl logs -n robusta -l app=robusta-runner --tail=50
```

주요 원인: Slack `api_key` / `slack_channel`이 설정되지 않았거나(또는 토큰이 취소됨); `api_key`가 Robusta 클라우드 릴레이 토큰(`robusta integrations slack`)인데 번들된 `disableCloudRouting: true`는 셀프 호스팅 Slack **봇 토큰**(`xoxb-…`)이 필요하거나 `disableCloudRouting: false`로 설정해야 함; 싱크 `scope`가 파드가 실행 중인 네임스페이스를 제외함(번들 값은 `agenteye`로 범위 지정); 또는 아직 실패가 발생하지 않음. 파드를 종료하여 테스트 경고를 강제 발생시키세요:

```bash theme={null}
kubectl -n agenteye delete pod -l app=clickhouse   # 다시 생성됩니다
```

설치 및 구성은 [enterprise-docs/health-monitoring.md](/ko/agenteye/health-monitoring#2-pod-failure-alerting-with-robusta-opt-in)를 참조하세요.

### 서버가 지속적으로 `NotReady` 상태를 반복하는 경우

준비 상태 프로브가 `/ready`를 호출하며, Postgres 또는 ClickHouse에 연결할 수 없을 때 실패합니다. 서버가 `NotReady`를 반복한다면 의존성이 간헐적으로 사용 불가능한 것입니다. ClickHouse와 Postgres 파드, 그리고 서버의 `CLICKHOUSE_URL` / `DATABASE_URL`을 확인하세요. `/ready`가 보고하는 내용을 확인합니다:

```bash theme={null}
kubectl -n agenteye exec deploy/server -- sh -c 'curl -s localhost:8080/ready'
```

이 프로브는 의도적으로 허용적입니다(관대한 실패 임계값). 따라서 지속적인 반복은 프로브가 너무 엄격한 것이 아니라 실제 의존성 문제를 나타냅니다. 활성 프로브는 `/health`에 남아 있으므로, 준비 상태 반복이 파드를 **재시작하지는 않습니다**.

## 인증서 모니터링 문제

### CronJob이 Slack 알림을 보내지 않는 경우

`cert-renewal-check` CronJob은 시크릿에 저장된 Slack 웹훅 URL이 필요합니다. 존재 여부를 확인하세요:

```bash theme={null}
kubectl get secret cert-renewal-notify-config -n agenteye
```

없으면 생성하세요:

```bash theme={null}
kubectl create secret generic cert-renewal-notify-config \
  --namespace agenteye \
  --from-literal=SLACK_WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
```

시크릿이 없어도 CronJob은 실행되어 stdout에 결과를 기록합니다. 로그 확인:

```bash theme={null}
kubectl logs -n agenteye -l job-name --tail=50
```

### 알림을 받기 전에 클라이언트 인증서가 만료된 경우

CronJob은 12시간마다 실행됩니다. 실행되지 않고 있다면 상태를 확인하세요:

```bash theme={null}
kubectl get cronjob cert-renewal-check -n agenteye
```

수동 확인을 트리거합니다:

```bash theme={null}
kubectl create job --from=cronjob/cert-renewal-check manual-cert-check -n agenteye
kubectl logs -n agenteye -l job-name=manual-cert-check
```

만료된 인증서를 즉시 재발급하려면:

```bash theme={null}
cd base/certificates/client-certs
./issue-client-cert.sh <cluster-name>
```

그런 다음 수집기를 실행하는 클러스터에 재생성된 `collector-mtls-secret.yaml`을 적용하고 재시작하세요:

```bash theme={null}
kubectl apply -f collector-mtls-secret.yaml -n <collector-namespace>
```

***

## 백업 문제

### `agenteye-backup`이 "No space left on device"로 실패하는 경우

`agenteye-backup` CronJob은 Postgres + ClickHouse를 `backup-tmp` `emptyDir` 임시 볼륨(기본값 `30Gi`)에 덤프한 다음, `tar` 아카이브를 S3에 **스트리밍**합니다. 압축된 아카이브는 임시 저장소에 기록되지 않으므로, 임시 저장소는 *원시 덤프*만 보관하면 됩니다. 파드 퇴거 / `No space left on device`는 **원시 덤프**가 임시 저장소 크기를 초과함을 의미합니다(ClickHouse `events` 덤프가 주된 원인이며 시간이 지날수록 증가합니다). 실패한 작업의 로그를 확인하세요:

```bash theme={null}
kubectl logs -n agenteye -l job-name=<failed agenteye-backup job>
```

해결 방법: 오버레이에서 CronJob의 `backup-tmp` `emptyDir` `sizeLimit`을 원시 덤프 총 크기보다 높이고, 노드의 임시 저장소가 실제로 이를 수용할 수 있는지 확인하세요(`sizeLimit`은 상한이지 예약이 아닙니다). 덤프가 단일 노드의 디스크를 초과하면 `backup-tmp`를 PVC(EBS/PD)로 교체하거나, 소스에서 덤프를 압축하세요.

> 이전 릴리스에서는 `.tar.gz`를 덤프와 동일한 `20Gi` 임시 저장소에 기록하여 `덤프 + 아카이브`가 초과되어 업로드 전에 파드가 퇴거됐습니다. 이는 S3 오류처럼 보이지만 실제로는 디스크 문제입니다. 업로드 스트리밍으로 이 두 배 문제가 해결됩니다.

### `agenteye-backup`이 `curl` 설치 실패로 실패하는 경우

작업이 `postgres:16` 이미지에서 실행되며 ClickHouse HTTP 덤프를 위해 시작 시 `curl`을 설치합니다. Debian 패키지 미러에 대한 이그레스가 없는 클러스터에서는 `apt-get` 단계가 실패합니다. 백업 파드의 이그레스를 허용하거나, `curl`이 포함된 미러링/커스텀 백업 이미지를 빌드하여 오버레이에서 참조하세요.

### `agenteye-backup`은 실행되지만 오브젝트 스토리지에 아무것도 저장되지 않는 경우

기본 구성은 실제 `BACKUP_BUCKET`(`ts-prod-agenteye/backups`)과 `agenteye-backup` ServiceAccount를 포함합니다. 작업은 아카이브를 S3에 **스트리밍**합니다(`tar cz … | aws s3 cp - s3://…`). 백업 파드가 버킷에 대한 쓰기 권한이 없으면 업로드 오류가 발생합니다. 스크립트가 `set -euo pipefail` 하에 실행되므로, 파이프 어느 곳에서든 실패하면 전체 작업이 `upload` 단계에서 실패합니다(파드의 EXIT 트랩이 `backup FAILED during step: upload`를 기록합니다). 임시 저장소 퇴거를 수정한 후에도 이 단계에 도달하므로, 이제 업로드가 정상적으로 저장되는지 확인하세요. 실패한 작업의 로그에서 S3 접근 오류를 검색하세요:

```bash theme={null}
kubectl logs -n agenteye -l job-name=<failed agenteye-backup job> | grep -iE 's3|upload|denied'
```

해결 방법: 오버레이에서 `BACKUP_BUCKET`을 소유한 버킷으로 설정하고, 기존 `agenteye-backup` ServiceAccount에 쓰기 권한을 주석으로 추가하세요(IRSA / Workload Identity / Pod Identity). [enterprise-docs/kubernetes-deployment.md](/ko/agenteye/kubernetes-deployment)의 **Backups** 섹션을 참조하세요.

***

## ClickHouse 기반 평가 / 세션 / 쿼리

### 업그레이드 후 `/queries` 페이지 사이드바가 비어 있는 경우

세 개의 테이블(`events`, `evaluations`, `agent_sessions`)이 있어야 합니다. 업그레이드 후 SchemaBrowser 사이드바가 비어 있다면, 서버가 시작 시 ClickHouse DDL 적용에 실패한 것입니다. `failed to apply CH DDL statement`가 있는지 서버 로그를 확인하세요:

```bash theme={null}
kubectl logs -n agenteye deploy/server | grep -E 'clickhouse|CH DDL'
```

가장 흔한 원인은 마이그레이션 실행 중 ClickHouse에 연결할 수 없는 경우입니다. 서버는 CH에 연결할 수 없으면 시작을 거부하므로, 멈춘 파드는 일반적으로 쿼리 페이지가 조용히 고장나는 것보다 `CrashLoopBackOff` 상태가 됩니다. 그러나 부분적인 DDL 적용(하나는 성공, 다음 5개는 5xx)은 스키마를 절반만 적용된 상태로 남깁니다. CH가 연결 가능함을 확인한 후 서버 파드를 재시작하세요:

```bash theme={null}
kubectl rollout restart deploy/server -n agenteye
```

### 새 평가가 `/sessions` 또는 `/queries`에 나타나지 않는 경우

업그레이드 후 새 평가는 Postgres가 아닌 ClickHouse에 기록되며, `/sessions`(`evaluations:read` 권한 필요)와 `/queries`에 표시됩니다. 표시되지 않는다면:

1. 평가자 파이프라인이 활성화되어 있고(`EVALUATOR_ENDPOINT`가 서버에 설정됨) 최종 결과를 생성하고 있는지 확인합니다. `evaluation_finalized` 로그 줄을 확인하세요.
2. 서버에서 CH에 연결 가능한지 확인합니다: `kubectl exec -n agenteye deploy/server -- curl -fsS http://clickhouse:8123/ping`.
3. CH 테이블을 직접 확인합니다: `kubectl exec -n agenteye clickhouse-0 -- clickhouse-client -q 'SELECT count() FROM agenteye.evaluations'`.

### 부하 시 쿼리가 "Memory limit exceeded"로 실패하거나 ClickHouse가 OOMKilled되는 경우

**증상:** 대시보드/쿼리 부하가 높을 때 분석 페이지(이벤트 스트림, `/sessions`, 모델/지연 시간 뷰, SQL 편집기)가 실패하거나 타임아웃됩니다. 서버가 잠깐 `NotReady` 상태가 되고 ClickHouse 파드의 재시작 횟수가 증가합니다. 거의 항상 **메모리** 문제이며 CPU나 디스크 문제가 아닙니다.

**메모리 문제 확인** (복제로 해결할 처리량 문제가 아님을 확인):

1. OOM 킬 여부를 파드에서 확인합니다:
   ```bash theme={null}
   kubectl -n agenteye describe pod clickhouse-0 | grep -iE 'Restart Count|Last State|Reason|OOMKilled'
   ```
   재시작 횟수가 증가하면서 `Reason: OOMKilled` / `Exit Code: 137`이 나타나면 이것이 원인입니다.

2. ClickHouse가 거부하고 있는 것을 확인합니다:
   ```bash theme={null}
   kubectl -n agenteye exec clickhouse-0 -- clickhouse-client -q \
     "SELECT name, value FROM system.errors WHERE value>0 ORDER BY value DESC"
   ```
   `MEMORY_LIMIT_EXCEEDED` 횟수가 많으면 해당 증상입니다. 메시지에 \*"maximum: N GiB"\*가 표시되는데, 이 **N은 파드 메모리 한도의 0.9배**입니다(`deploy/base/clickhouse/configmap.yaml`의 `max_server_memory_usage_to_ram_ratio`). 대용량 읽기가 N을 초과하면 거부됩니다.

3. 문제가 **아닌** 것들을 배제합니다. CPU, 파트 수, 디스크가 모두 낮다면 레플리카/샤딩 추가는 낭비입니다:
   ```bash theme={null}
   kubectl -n agenteye top pod clickhouse-0
   kubectl -n agenteye exec clickhouse-0 -- clickhouse-client -q \
     "SELECT table, count() parts FROM system.parts WHERE active AND database='agenteye' GROUP BY table"
   ```

**원인:** ClickHouse 파드의 메모리 한도가 분석 작업 집합에 비해 너무 작습니다. 가장 무거운 읽기는 원시 JSON `payload` 컬럼을 가져와 `JSONExtract*`를 실행하고 `FINAL`을 사용하며, 각각 수 GiB가 필요할 수 있습니다. 구성된 캐시(`mark_cache_size` + `uncompressed_cache_size`)가 파드보다 크면 동일한 예산을 소비하여 쿼리 메모리를 압박합니다.

**해결 방법 — ClickHouse 메모리 확장:**

1. 오버레이에서 `clickhouse` StatefulSet의 컨테이너 `resources`를 패치하여 ClickHouse 메모리 한도를 높이세요(다른 컴포넌트의 `resources`에 사용하는 오버레이 메커니즘과 동일). 사용 가능한 서버 예산은 `0.9 × 한도`입니다. `6Gi` 한도는 약 5.4GiB, `16Gi` 한도는 약 14GiB입니다. `requests.memory`도 실제 최솟값으로 설정하여 스케줄러가 이를 예약하도록 하세요. 이를 적용하면 **CH 파드가 재생성됩니다**(단일 레플리카 → 약 30\~60초의 분석 다운타임). 트래픽이 적은 시간에 수행하세요.
2. \`deploy/base/clickhouse/configmap
