Sync the #ai-platform-internal-qa Slack channel (demo.thakicloud.net QA reports) to a local corpus with full threads, filter out completion-tagged (완료/확인_완료) items, and dedup-check a new QA finding before posting it in the team's expert template. Use when "QA 이슈 올리기 전 중복 체크", "내부 QA 슬랙 싱크", "demo qa 중복 확인", "이 버그 이미 올라왔나", "internal-qa 채널", "check if this QA issue is a duplicate", "post QA finding". Do NOT use for GitHub issue dedup (use github-issue-dedupe), monitor/alert spam throttling (use slack-alert-dedup-cooldown), or general Slack posting (use scripts/slack_post_message.py).
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Sync the #ai-platform-internal-qa Slack channel (demo.thakicloud.net QA reports) to a local corpus with full threads, filter out completion-tagged (완료/확인_완료) items, and dedup-check a new QA finding before posting it in the team's expert template. Use when "QA 이슈 올리기 전 중복 체크", "내부 QA 슬랙 싱크", "demo qa 중복 확인", "이 버그 이미 올라왔나", "internal-qa 채널", "check if this QA issue is a duplicate", "post QA finding". Do NOT use for GitHub issue dedup (use github-issue-dedupe), monitor/alert spam throttling (use slack-alert-dedup-cooldown), or general Slack posting (use scripts/slack_post_message.py).
QA reporters post demo-app bugs to #ai-platform-internal-qa (C0ABMR6CH42) in a
fixed template (환경: / 이슈: / 담당자:). Before posting a new finding you
must know it isn't already reported. This skill syncs the channel + threads
locally, then dedup-gates new posts.
Format is code-owned (parsing, status enum, dedup scoring, mrkdwn render) per
[[sonnet-format-determinism]] and [[thin-harness-fat-skills]]; the model only
judges borderline dup matches. Slack access is repo-local SLACK_USER_TOKEN
(Path B, [[mcp-graceful-degradation]]) so it runs headless. Posting is a gated
external action (Safety Gates): explicit approval required.
게시 단위 = 관찰된 현상 1개 (근본 원인 1개가 아니다)
리포트 단위는 "관찰한 증상(현상)"이지 "근본 원인"이 아니다. QA는 현상을 보고 올리고
(예: "엔드포인트가 안 뜨고 계속 있다"), 그 시점엔 근본 원인이 대개 미상이다. 하나의 현상 =
하나의 포스트 = 하나의 스레드. 리포터에게 근본 원인별 사전 분해를 강요하지 않는다.
한 현상을 RCA하다 1차 원인 + 2차(부차)·구조적 발견이 같이 나오면, 같은 스레드에
1차 원인 / 2차(부차) 발견으로 명확히 라벨해 함께 둔다. 이건 여전히 이슈 1개다.
별도 포스트로 쪼개는 건 진짜 다른 현상(다른 증상·무관한 재현경로)일 때만이다.
같은 증상의 다른 근본 원인은 쪼개는 기준이 아니다.
부차 발견이 독립 트래킹(별도 완료/확인_완료)이 필요하면, 그건 담당자가 후속
티켓/스레드로 분리하고 원 스레드에 링크한다 — 리포터의 포맷 부담이 아니라 담당자의 triage다.
2026-07-08 경위: 한 현상(35B 엔드포인트가 안 뜸)의 RCA에서 1차(S3 1초 저속-타임아웃) +
2차(resume watcher가 CrashLoop을 fast-fail로 못 내림)가 같이 나왔다. 이 둘을 한 스레드에
담은 건 "한 현상"으로선 맞다. 담당자가 "1이슈:1스레드"를 요청했고 직전 커밋이 이를
"근본원인별로 N개로 쪼개라"로 박았으나 — 그건 과교정이다. 리포트 단위는 현상이고,
부차 이슈는 리포터가 쪼개는 게 아니라 담당자가 후속으로 분리·해결하는 게 맞다. 되돌린다.
(담당자 측 valid point = 완전히 무관한 두 버그가 한 스레드면 per-thread 상태태그가 깨진다 →
그래서 "같은 현상이면 1차/2차 라벨로 한 스레드, 무관한 현상이면 별 스레드"로 절충.)
Status model (empirical, from the channel's pinned 공지)
Dedup checks against all synced issues including done — reposting a
fixed-and-closed issue is still redundant. "작업 중" view excludes done.
Workflow
Sync (do this first, and re-run before any dedup check to stay fresh):
.venv/bin/python .claude/skills/qa-issue-dedup-poster/scripts/qa_slack.py \
sync --since 2026-06-29 --json
# weekly/trend context: add later --since; corpus = state/corpus.jsonl (gitignored)
Prints total / active counts + status breakdown. Fetches every parent + full
thread (resolution replies included).
Dedup-check a candidate (retrieval is code; you make the final call):
.venv/bin/python .claude/skills/qa-issue-dedup-poster/scripts/dedup.py \
--issue "metis hub > model registry 필터 검색 옵션 누락" --json
Score blends token overlap + feature-path (A > B > C) overlap.
score ≥0.5 = likely_dup, 0.3–0.5 = review, <0.3 = unlikely. On a
review verdict, read the matched issue + its thread and decide semantically
(same root cause vs superficially similar).
Duplicate → thread-append, not drop. A dup is not discarded — the new
detail is posted as a reply in the existing issue's thread (recurrence /
deeper RCA / new evidence) so one thread accumulates the full story instead of
the channel fragmenting into near-duplicates:
likely_dup (score ≥0.5) → post.pyauto-appends to the matched thread.
review/unlikely but you judged it the SAME issue (semantic match the
score missed — e.g. a closed issue that regressed) → pass the matched
issue's ts via --reply-to <ts> to append there.
genuinely new → normal top-level post (no --reply-to, not a dup).
want a NEW top-level despite a dup → --force.
Each corpus/dedup match carries a ts (the parent message) — that's the
--reply-to value.
Humanize the body text first (required), THEN enforce 존댓말 (required).
Before rendering, run the issue / location / actual / root-cause prose through
the humanizer skill so the post reads like a person wrote it — natural
sentences, no AI-slop, varied endings ([[report-writing-humanize]]).
⚠️ humanizer outputs 평서체 (plain -다 endings that read as 반말) — it does
NOT do 경어/register. This is a team channel, so after humanizer you MUST
chain korean-tone-reviewer to convert the field text to 존댓말 (합니다체
-습니다; 명령형 개조식 재현 경로는 그대로 허용). Skipping the tone step ships a
반말 post (2026-07-14 사고: 휴머나이저만 돌려 반말로 게시 → 담당자 지적). The
scaffold (labels, bullets, links) stays code-owned; only the field text is
humanized + tone-reviewed. QA must be able to (a) locate the bug and (b)
understand the cause without re-triage, so always fill --location
(화면/계층/URL/코드 위치) and --root-cause (확인된 로그·재현 근거; 추정이면
[추정]). Confirmed-vs-hypothesis must be honest ([[news-freshness-and-sourcing]]).
Render + post (dry-run first; --post is gated). A likely_dupauto-appends to the matched thread; add --reply-to <ts> to append a
lower-scored match you judged identical, or --force to post fresh:
.venv/bin/python .claude/skills/qa-issue-dedup-poster/scripts/post.py \
--app "Metis Serve" --title "<한눈에 파악되는 짧은 제목>" \
--env prod --issue "<humanized one-line issue>" \
--location "<앱>화면 > 위치 · 계층(예: 백엔드 chat.go:157 → KEDA interceptor)" \
--steps "step1; step2; step3" --expected "<expected>" --actual "<actual>" \
--root-cause "<확인된 원인 or [추정]>" \
--domain 백엔드 --domain 클러스터큐 --ref "https://…|화면 설계" \
--screenshot /tmp/repro.png # dry-run# → review draft + dedup, then re-run with --post to send
담당자는 내용의 도메인으로 자동 태그 (--domain, 한/영 별칭 가능): 인프라→박윤재,
기획→김해림, 프론트→신찬우·이민기, 백엔드→조태양, ml→유두열, 클러스터큐(kueue/keda)→야키요.
여러 도메인이면 모두 태그되고, 매핑에 없는 도메인은 조용히 무시하고 경고만 낸다(임의 배정
금지 — [[sra-incorporation-guard]]). 특정인을 직접 넣으려면 --assignee <id> (둘은 합쳐짐).
매핑은 references/assignees.json 한 곳에서 관리 — 담당자/도메인 변경은 그 파일만 고친다.
Rendered layout (code-owned scaffold): a bold at-a-glance 제목🐛 *[{app}] {title}* on top so readers know the app + situation instantly,
then each section with its label bolded (*환경:*, *이슈:*, *위치:*,
*재현 경로:*, *기대/실제 동작:*, *원인(추정):*, *참고:*, *담당자:*) and a
blank line between sections for fast scanning. Always pass --app (which
app) and --title (short headline); if --title is omitted it's derived from
the issue.
긴 필드는 한 문장으로 잇지 말고 테마 불릿으로 (가독성, 2026-07-14 지적). 길거나
구조적인 --actual/--root-cause(예: 1차/2차/별개/검증 원인)는 문장을 이어붙이지 말고
\n으로 • 테마 불릿을 만들어 넘긴다(불릿 안 미니라벨 *1차 원인(확정):* 권장). render는
본문에 \n이 있으면 라벨을 제 줄에 두고 그 아래에 본문을 붙인다(한 줄 필드는 인라인 유지) —
"문장마다 빈 줄"이 아니라 "논리 덩어리마다 불릿"이 정답. steps는 ; 구분이 자동 번호줄이 된다.
Renders the team's expert template (mrkdwn, [[slack-post-formatting]]) and
posts as the user via scripts/slack_post_message.py. Get user approval on
the draft before --post.
Attach a screenshot to the thread (do this whenever a screen is involved).
Pass --screenshot <path> (repeatable) — after the parent posts, post.py
uploads each image to that message's thread via scripts/slack_upload_file.py
(thread_ts, SLACK_USER_TOKEN). Prefer a browser capture of the actual
error screen (demo.thakicloud.net): use agent-browser / carbonyl-browser
when a browser + demo session is available. If the app screen can't be reached
headlessly (no browser binary / no auth), fall back to a capture of the
reproduction evidence (CLI repro, backend error log) and say so — never
fabricate a UI shot. The screenshot rides the thread so the visual sits with
the issue.
Notes / gotchas
Never post without an explicit finding from the user + approval on the draft.
This is a real team channel; the skill only automates the dedup gate + format,
not the decision to post.
Post reads human AND 존댓말, not templated. Body text goes through humanizer
first, then korean-tone-reviewer to force 존댓말 (합니다체) — humanizer alone
ships 반말 (2026-07-14 사고). --location/--root-cause are filled so QA locates +
understands the bug in one read. Screen bugs get a --screenshot attached to the thread (browser capture of
the error screen preferred; CLI/log repro as honest fallback).
The 이슈: line is the dedup key; the > feature path dominates scoring —
keep new issues in the same App > 화면 > 위치 shape for best matching.
게시 단위 = 관찰된 현상 1개(위 절). 한 현상의 RCA에서 나온 1차/2차 원인은 같은
스레드에 라벨해 둔다(근본원인별로 쪼개지 않는다). 진짜 다른 현상만 별 포스트. 부차 이슈의
독립 트래킹이 필요하면 담당자가 후속 티켓으로 분리 — 리포터가 사전 분해할 일 아니다.
assignees are captured by Slack user-id; look up display names via
slack_read_user_profile if you need to name them.
Re-sync is cheap and idempotent (corpus is rebuilt each run). Always sync
before a dedup check so newly-posted issues are in scope.
Corpus holds internal QA content (member ids) → state/ cache is gitignored;
only skill code is committed ([[token-diet-hygiene]]).