From 6badd5c9f0001c88d652a4a09e149ce083a2a223 Mon Sep 17 00:00:00 2001 From: hbyang Date: Fri, 18 Sep 2026 13:26:38 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20solution/backend:=20=ED=94=8C=EB=9E=AB?= =?UTF-8?q?=ED=8F=BC=20=EA=B1=B0=EC=A0=88=EC=97=90=20=EC=82=AC=EC=9C=A0?= =?UTF-8?q?=EB=A5=BC=20=EB=B6=99=EC=9D=B8=EB=8B=A4=20=E2=80=94=20THREADS?= =?UTF-8?q?=5FREJECTED=5F400=20=EB=A7=8C=EC=9C=BC=EB=A1=9C=EB=8A=94=20?= =?UTF-8?q?=EB=AA=BB=20=EA=B3=A0=EC=B9=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 연동이 실패했는데 로그에 `THREADS_REJECTED_400` 만 남았다. 코드가 만료됐는지, 리디렉션 URI 가 안 맞는지, 권한이 모자란지 구별이 안 돼 원인을 세 번 헛짚었다(실측 2026-09-18). Meta 는 응답 본문에 `error.message` 와 `error_subcode` 로 이유를 정확히 말해 주는데, 우리가 그걸 읽고 버리고 있었다. - external/threads._read: 거절 코드 뒤에 `[subcode] message` 를 붙인다 - ★ 담는 것은 message·subcode 뿐이다. 토큰·시크릿·인가 code 는 담지 않는다 — 이 문자열은 로그로 가고 로그는 우리가 아닌 사람도 본다. message 는 160자에서 끊는다 검증: SNS 테스트 14건 통과. 실제 호출로 엔드포인트·자격증명이 정상임을 먼저 확인했다 (더미 code 로 `Invalid verification code` 응답 · debug_token·장기토큰 교환 호출 모양 정상) Co-Authored-By: Claude Opus 5 (1M context) --- solution/backend/services/external/threads.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/solution/backend/services/external/threads.py b/solution/backend/services/external/threads.py index aa74304..1f69187 100644 --- a/solution/backend/services/external/threads.py +++ b/solution/backend/services/external/threads.py @@ -42,8 +42,18 @@ def _read(res): raise SocialError("THREADS_INVALID_RESPONSE") from ex if res.status_code >= 400 or data.get("error"): error = data.get("error") or {} + # ★ 플랫폼이 준 사유를 코드에 붙인다. `THREADS_REJECTED_400` 만으로는 무엇이 틀렸는지 + # 알 수 없어서 — 코드가 만료됐는지, 리디렉션 URI 가 안 맞는지, 권한이 모자란지 — + # 실제로 원인을 좁히지 못했다(실측 2026-09-18: 연결 실패가 400 이라는 것만 알고 + # Meta 가 뭐라고 했는지는 어디에도 안 남아 세 번을 헛짚었다). + # ★ 남기는 것은 message·subcode 뿐이다. 토큰·시크릿·code 는 담지 않는다 — + # 이 문자열은 로그로 가고, 로그는 우리가 아닌 사람도 본다. + detail = str(error.get("message") or "")[:160] + subcode = error.get("error_subcode") or error.get("code") raise SocialError( - f"THREADS_REJECTED_{res.status_code}", + f"THREADS_REJECTED_{res.status_code}" + + (f"[{subcode}]" if subcode else "") + + (f" {detail}" if detail else ""), reauth=error.get("code") == 190 or res.status_code == 401, ) return data