2026.09.20(일)

inote-ai 자동 요약 미동작 트러블슈팅

증상

inote에서 글을 저장(발행)해도 Groq 기반 AI 자동 요약이 생성되지 않음. 에러 메시지도 없이 그냥 조용히 안 됨.

원인 파악 과정

1단계 — 관련 코드 확인

blog.service.ts의 발행 로직을 확인:

async update(userId: string, id: string, dto: UpdatePostDto) {
  ...
  if (publish) {
    await this.summarize(updated.id, updated.title, updated.content);
  }
  return updated;
}

private async summarize(postId: string, title: string, content: string) {
  try {
    const res = await fetch(`${process.env.INOTE_AI_URL}/summarize`, {
      method: 'POST',
      headers: { 'x-internal-secret': process.env.INTERNAL_SECRET ?? '', ... },
      ...
    });
    if (!res.ok) throw new Error(...);
    ...
  } catch (e) {
    this.logger.warn(...);   // ← 에러를 삼키고 로그만 남김 (fail-open 설계)
  }
}

→ 여기서 try/catch로 에러를 삼키는 fail-open 설계라는 걸 확인. "글 저장 자체는 AI 서버 문제로 막히면 안 된다"는 의도된 설계이지만, 부작용으로 실패해도 사용자에게 전혀 안 보임 → 눈에 보이는 증상("조용히 안 됨")과 정확히 일치.

2단계 — 의심 지점 좁히기

process.env.INTERNAL_SECRET ?? '' 부분에 주목 — 값이 없으면 빈 문자열이 헤더로 나감. 로컬 .env 확인 결과 INTERNAL_SECRET 항목이 아예 없었음 (이전에 환경변수 정리할 때 실수로 빠뜨림).

3단계 — 직접 재현해서 확인

배포된 inote-ai에 빈 시크릿으로 직접 요청:

curl -X POST <https://inote-ai.onrender.com/summarize> \
  -H "x-internal-secret: " \
  -d '{"title":"test","content":"..."}'
# → 401 {"detail":"unauthorized"}

→ 원인 확정.

4단계 — 값 확보 후 검증

사용자가 올바른 시크릿 값을 찾아서 전달. 같은 요청을 정확한 값으로 다시 보내니:

# → 200 {"summary": ["...", "...", "..."]}

→ 값 자체가 맞다는 것까지 확인.