feat(council): 이종 벤더 3인 심의 플러그인 — codex + agy + Opus 좌석 - #189
Conversation
Claude 서브에이전트를 여러 개 띄우는 것은 관점을 늘리지 못한다. 가중치를 공유하므로 같은 방식으로 틀린다. core-config 가 이미 [council] 힌트 줄을 매 프롬프트에 주입하고 있었으나 위임을 실제로 실행하는 경로는 없어, 매번 손으로 명령을 조립해야 했다. /council:convene 이 하나의 질문을 세 벤더의 모델에게 던진다. codex(gpt-5.6-sol), agy(gemini-3.6-flash-high), Claude(opus) 가 독립 의견을 내고, 각자 사용자에게 확인이 필요한 것을 제출하면 의장이 그걸 병합해 한 번 되묻고, 그 답을 받아 서로를 반박한 뒤 의장이 합성한다. 의장은 메인 세션이며 의석이 아니다. Claude 의석이 의장과 같은 모델이라는 대가는 두 가지로 갚는다. 동족 합의 할인(의장과 같은 결론을 냈다는 사실만으로는 합의 근거로 세지 않고 새 논거를 가져왔을 때만 계수)과 적대 역할 부여다. 그럼에도 opus 인 이유는 라운드 2 가 공격 과제라서, 약한 의석은 약한 반박을 내고 그건 의장이 걸러 기권이 되기 때문이다. 좌석 모델은 ~/.claude/council-models.json 에 주간 TTL 로 고정한다. 양쪽 CLI 모두 모델 목록이 기계 판독 가능하지만(models_cache.json, agy models) 자동 승급은 하지 않는다 — 새 모델이 실제로 나은지의 판단은 사용자 것이고, 조용한 승급은 의도하지 않은 비용으로 흐른다. 만료 시 실제 목록을 근거로 제시하며 항상 묻는다. 사전 컨텍스트는 파일 경로로 환원되지 않는 것만 담는다. codex 와 agy 는 파일을 스스로 읽으므로 AGENTS.md 나 .llmwiki 는 경로만 준다. 담는 것은 mem0 기억(MCP 뒤라 파일이 아님), Serena 심볼 그래프(LSP 색인이 있어야 나옴), code-scout 리서치(레포 바깥) 셋이다. 실행으로 확인한 사실: - codex 는 잘못된 모델명에 매달리지 않고 몇 초 만에 HTTP 400 으로 죽는다. 모델명 노후화는 행잉 원인이 아니다. - check_for_update_on_startup 은 실재하는 codex 설정 항목이다. --strict-config 통과 / 가짜 키는 unknown configuration field 거부로 양성·음성 대조를 마쳤다. - agy 는 이 환경에서 headless 로 정상 동작한다(1.1.7). 문서가 미해결이라 적은 stdout 유실은 관측되지 않았으나, 파일 쓰기 우회는 비용이 0 이고 실패했을 때만 손해가 나는 비대칭이라 유지한다. - codex 는 프롬프트를 stdin(`-`)으로 받고 답을 -o 파일로 낸다. stdout 에는 훅 로그와 토큰 수가 섞이므로 파싱하지 않는다. convene 은 번들 스크립트가 없어 호출 계약이 SKILL.md 안에만 존재하고, 따라서 아무도 실행하지 않는다. agy 호출에서 < /dev/null 이 빠지면 그 의석이 영원히 멈추는데 --print-timeout 도 이를 막지 못하고, GNU 전용 구문이 아니라서 check-shell-portability 의 관심사도 아니다. 픽스처 스위트를 붙이고 pre-commit + CI(GNU·macOS 양쪽)에 배선했다 — 배선하지 않은 테스트는 장식이다. Codex 매니페스트에서는 제외한다(CODEX_EXCLUDED). Codex 에서 돌리면 codex 가 자기 자신을 의석으로 소환하는 순환이고, Claude 의석이 필요로 하는 Agent 도구가 Codex 에는 없다. codex-image 와 같은 논리다. 자율 토론 모드(agent teams)는 의도적으로 뺐다. 기술적으로 가능함은 확인했으나 팀원이 사용자에게 질문할 수 없어 재질문 관문과 충돌한다. 조사 결과는 스펙의 "보류된 갈래" 절에 남겨 다시 꺼낼 때 처음부터 조사하지 않게 했다. Closes #188
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 3 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds a Claude-only ChangesCouncil Plugin
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant User
participant Chair
participant Codex
participant Agy
participant ClaudeSeat
participant CouncilLogs
User->>Chair: Invoke /council:convene
Chair->>Codex: Collect Round 1 opinion
Chair->>Agy: Collect Round 1 opinion
Chair->>ClaudeSeat: Collect Round 1 opinion
Chair->>User: Submit merged follow-up questions
User-->>Chair: Provide answers
Chair->>Codex: Send peer opinions and user answers
Chair->>Agy: Send peer opinions and user answers
Chair->>ClaudeSeat: Send peer opinions and user answers
Chair->>CouncilLogs: Write seat logs and consensus.md
Chair->>User: Return synthesis summary
🚥 Pre-merge checks | ✅ 9✅ Passed checks (9 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1b2f01fb19
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| codex exec -s read-only --skip-git-repo-check \ | ||
| -m "$CODEX_MODEL" \ | ||
| -c model_reasoning_effort="\"$CODEX_EFFORT\"" \ | ||
| -c service_tier="\"$CODEX_TIER\"" \ | ||
| -o "$DIR/r1-codex.md" - < "$DIR/r1-prompt.md" |
There was a problem hiding this comment.
Step 0과 Step 1의 Bash 블록을 별도 도구 호출로 실행하면 CODEX_MODEL·CODEX_EFFORT·CODEX_TIER와 DIR가 다음 호출까지 유지되지 않습니다. 따라서 문서대로 이 블록에 도달하면 빈 모델명과 출력 경로가 전달되어 첫 codex 좌석부터 실패합니다. 레지스트리와 현재 실행 디렉터리를 각 호출에서 다시 읽거나, 호출 간 유지되는 파일에 저장하도록 하세요.
AGENTS.md reference: AGENTS.md:L196-L196
Useful? React with 👍 / 👎.
| When writing the registry, also make sure `~/.codex/config.toml` carries | ||
| `check_for_update_on_startup = true`. Read the file, and only append the line when the key is | ||
| absent — never rewrite keys the user set. In the same breath as the weekly pin question, offer |
There was a problem hiding this comment.
config.toml이 [mcp_servers.foo] 같은 테이블로 끝나는 일반적인 경우 파일 끝에 이 줄을 append하면 check_for_update_on_startup이 최상위 설정이 아니라 마지막 테이블의 하위 키가 됩니다. 이 경우 업데이트 설정이 적용되지 않고 strict config 검증이 기존 Codex 설정을 거부할 수도 있으므로, 첫 테이블 선언 앞에 삽입하거나 TOML-aware 방식으로 최상위 키를 갱신해야 합니다.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/spec/2026-07-29-council.md:
- Around line 7-8: Update the council specification’s overview and glossary
references to use Claude Opus consistently for the chair/main session instead of
Claude Sonnet 5, including the corresponding references around the additionally
flagged section. Preserve the existing seating contract terminology and ensure
all descriptions align with the `opus` model identifier.
In `@plugins/council/skills/convene/SKILL.md`:
- Line 241: Update the output restriction in the convene instructions to include
결석 alongside 합의, 갈린 이견, and 미해결, while continuing to keep the full record in the
files.
- Around line 84-96: Update the jq registry-generation flow around the
council-models.json output to write to a temporary file first, then move that
file into place only after jq and date succeed. Preserve the existing
destination and JSON content, and ensure failures leave the current registry
unchanged.
- Line 55: Update the expiration condition in the pin state logic to treat
elapsed time equal to TTL as expired by using an inclusive boundary check.
Preserve the existing fresh state for values below TTL and verify the exact
seven-day boundary behavior.
- Around line 264-266: Update the log-triage commands in the convene skill to
stop automatically printing raw Antigravity log contents via tail. Replace this
behavior with fixed status messages for recognized failure conditions, and only
display raw logs after explicit user opt-in.
- Around line 74-75: Update the agy discovery command in the convene skill so
only a missing agy executable produces “(agy absent)”; propagate or visibly
report failures from an executed agy models invocation, including
authentication, network, and CLI errors, and prevent registry confirmation from
proceeding without the real candidate list.
- Around line 128-133: Validate SLUG before assigning BASE or constructing any
filesystem path, accepting only the documented short kebab-case format and
rejecting empty, absolute, slash-containing, traversal, or otherwise invalid
values with a clear error and nonzero exit. Keep the existing date-based naming
and collision handling unchanged after validation.
In `@plugins/council/skills/convene/tests/run-tests.sh`:
- Around line 62-82: The tests currently duplicate the Step 0 registry writer
and only indirectly inspect SKILL.md, so regressions in the real TTL/schema
logic can pass. Update the test setup around state_of and the registry creation
block to execute or validate the actual Step 0 implementation from SKILL.md,
asserting its complete contract before running the TTL cases; remove reliance on
the copied jq writer.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 41638234-172b-4365-a189-45f664ba6e90
📒 Files selected for processing (13)
.claude-plugin/marketplace.json.claude/settings.json.claude/spec/2026-07-29-council.md.githooks/pre-commit.github/workflows/validate-codex.ymlAGENTS.mdREADME.mdplugins/council/.claude-plugin/plugin.jsonplugins/council/CLAUDE.mdplugins/council/skills/convene/SKILL.mdplugins/council/skills/convene/tests/run-tests.shscripts/check-skill-tool-portability.mjsscripts/manifest-eligibility.mjs
CodeRabbit 8건 + Codex P1 2건, 전부 real 판정. defer/skip 없음. 기능 정확성: - 스펙 개요와 용어 사전이 Sonnet 5 로 남아 좌석 계약(Opus)과 모순이었다. 스펙이 이 설계의 SoT 라 그대로 두면 읽는 쪽이 좌석 비용과 동족 합의 할인의 전제를 잘못 이해한다. - Step 5 가 채팅 요약에 합의·이견·미해결만 허용해, 결석을 명시하라는 실패 정책과 자기모순이었다. 2석 결과가 3석짜리로 읽히는 바로 그 경우다. - TTL 비교가 -gt 라 정확히 7일 시점에 fresh 를 유지했다. -ge 로 바꾸고 경계 케이스를 테스트에 넣었다. 종료 상태 삼킴 (code_review.md P1): - `command -v agy && agy models || echo "(agy absent)"` 는 인증 만료도 네트워크 오류도 전부 "설치 안 됨"으로 보고한다. 그러면 후보 목록 없이 핀 확인 질문을 던지게 된다. 같은 파일의 codex 캐시 읽기도 동일 패턴이라 같이 고쳤다(9c.6 같은 파일 일반화). CR 제안의 exit 1 은 좌석이 독립적으로 실패한다는 원칙과 충돌해 비치명적 보고로 조정했다. 데이터 손실: - 레지스트리 쓰기가 bare > 라 jq 실행 전에 파일을 truncate 했다. jq 가 실패하면 멀쩡한 핀이 사라지고 다음 실행이 조용히 첫 실행 기본값으로 돌아간다. 임시 파일 경유로 바꾸고, jq 를 실패시키는 shim 으로 이전 내용 보존을 검증했다. 신뢰 경계: - SLUG 이 자유 텍스트에서 와 경로에 닿는데 검증이 없었다. 슬래시나 .. 하나로 실행 디렉터리와 그 안의 모든 프롬프트·로그가 .council 바깥에 쓰인다. - agy 실패 분류가 로그 40줄 원시 덤프였다. 알려진 시그니처 3종 grep 으로 대체했다. 분류 능력은 그대로면서 노출이 줄고 코드도 더 작다. Codex P1 2건: - 셸 변수는 도구 호출 사이에 유지되지 않는다. Step 0/1 이 정한 모델명과 실행 디렉터리가 좌석 호출 시점에는 이미 없어서, 문서대로 따라가면 첫 좌석부터 빈 인자로 실패한다. 실행 포인터를 .claude/state 에 두고 각 좌석 블록이 레지스트리에서 핀을 다시 읽도록 했다. 이 PR 의 cr-fix 를 직접 돌리면서 같은 문제를 겪어 재현을 확인했다. - check_for_update_on_startup 을 config.toml 끝에 append 하면 마지막 테이블의 하위 키가 된다. 사용자 실제 파일이 [hooks.state...] 로 끝나는 것을 확인했다. 첫 테이블 앞에 삽입하도록 바꾸고 tomllib 로 최상위 키임을 파싱 검증했다. 테스트 (가장 날카로운 지적): - 스위트가 SKILL.md 의 TTL 로직을 손으로 복사해 실행하고 있었다. 실제 블록이 깨져도 복사본은 통과하므로 CI 가 초록으로 남는다. 내가 스스로 우려했던 드리프트 표면을 만들어 놓고 지나쳤다. awk 로 SKILL.md 의 fence 를 추출해 실제 블록을 throwaway HOME 에서 실행하도록 바꿨다. SKILL.md 의 -ge 를 -gt 로 되돌리면 경계 케이스가 깨지는 것으로 음성 대조했다. 17 → 26 케이스. 플러그인 버전은 0.1.0 그대로 둔다. 0.1.0 은 아직 머지되지 않아 캐시된 사용자가 없으므로, 같은 PR 안의 리뷰 수정마다 범프하면 릴리스되지 않은 버전만 늘어난다.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6fcd2f27b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if jq -n \ | ||
| --arg cm "$CODEX_MODEL" --arg ce "$CODEX_EFFORT" --arg ct "$CODEX_TIER" \ | ||
| --arg am "$AGY_MODEL" --arg clm "$CLAUDE_MODEL" \ | ||
| --argjson now "$(date +%s)" --arg today "$(date -u +%Y-%m-%d)" ' |
There was a problem hiding this comment.
레지스트리가 없거나 만료된 경우 사용자에게서 받은 값은 이전 도구 호출의 셸 변수일 뿐인데, 이 블록은 CODEX_MODEL 등 다섯 변수를 다시 설정하지 않은 채 jq --arg에 넘깁니다. 따라서 문서의 블록을 그대로 실행하면 모든 좌석 핀이 빈 문자열인 유효 JSON이 저장되고 이후 호출이 전부 실패합니다. 기존 상태 복원 지적 이후 Step 2 좌석 블록은 수정됐지만, 새 근거로 이 writer는 여전히 복원하지 않으며 테스트도 실제 흐름과 달리 변수를 환경에 직접 주입해 결함을 가립니다. 선택 결과를 지속 상태에 보관한 뒤 이 블록에서 다시 읽거나, 같은 블록 안에서 명시적으로 할당하세요.
Useful? React with 👍 / 👎.
| and catches a config key that a codex upgrade removed: | ||
|
|
||
| ```bash | ||
| codex exec --strict-config -s read-only --skip-git-repo-check \ |
There was a problem hiding this comment.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugins/council/skills/convene/SKILL.md (1)
106-122: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winCreate the registry temp file on the destination filesystem.
mktemp "${TMPDIR:-/tmp}/..."may place the temporary file on a different filesystem from$HOME/.claude;mvcan then fail or fall back to a non-atomic copy. Create the temp file under$HOME/.claudebefore renaming it over the registry.Proposed correction
mkdir -p "$HOME/.claude" tmp=$(mktemp "${TMPDIR:-/tmp}/council-reg-XXXXXX") +tmp=$(mktemp "$HOME/.claude/council-reg-XXXXXX")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/council/skills/convene/SKILL.md` around lines 106 - 122, Update the registry creation flow around REG and tmp so mktemp creates the temporary file inside the already-created $HOME/.claude directory, ensuring mv "$tmp" "$REG" remains an atomic same-filesystem rename. Keep the existing jq generation and cleanup behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/spec/2026-07-29-council.md:
- Line 7: Update the Korean spacing in the council description so “Anthropic 의
Claude Opus 이며” becomes “Anthropic의 Claude Opus이며,” while preserving the
surrounding meaning and wording.
In `@plugins/council/skills/convene/SKILL.md`:
- Around line 52-57: Update the registry freshness check around NOW, CHECKED,
and TTL to always use a fixed seven-day TTL instead of the registry’s ttl_days
value. Validate checked_at_epoch and mark the pin expired when it is malformed,
zero/invalid, or in the future; otherwise retain the existing -ge boundary so
exactly seven days is expired and requires confirmation.
- Around line 135-157: Update the configuration block that manages CFG so it
creates ~/.codex/config.toml when absent, inserts check_for_update_on_startup =
true before the first table header, and performs the change atomically. On any
directory, temporary-file, or move failure, stop or explicitly verify the
setting rather than continuing; ensure the later probe cannot succeed while the
mandatory key is missing.
In `@plugins/council/skills/convene/tests/run-tests.sh`:
- Around line 133-136: Update the failing-writer test around the bash -c
"$WRITER" invocation to handle its expected nonzero status explicitly under
strict mode: wrap the command in an if/else, assert failure in the appropriate
branch, and then verify that the registry still matches before. Preserve the
existing writer environment and registry-content assertion.
---
Outside diff comments:
In `@plugins/council/skills/convene/SKILL.md`:
- Around line 106-122: Update the registry creation flow around REG and tmp so
mktemp creates the temporary file inside the already-created $HOME/.claude
directory, ensuring mv "$tmp" "$REG" remains an atomic same-filesystem rename.
Keep the existing jq generation and cleanup behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: bd648fe0-0eba-4eef-a245-edb8b24c8881
📒 Files selected for processing (3)
.claude/spec/2026-07-29-council.mdplugins/council/skills/convene/SKILL.mdplugins/council/skills/convene/tests/run-tests.sh
|
|
||
| 서로 다른 회사가 만든 세 개의 언어 모델에게 같은 질문을 던지고, 그 셋이 서로의 답을 읽고 반박한 뒤, | ||
| 합의된 결론과 남은 이견을 하나의 문서로 정리해 주는 플러그인이다. 세 모델은 각각 OpenAI 의 codex, | ||
| Google 의 agy(Antigravity), Anthropic 의 Claude Opus 이며, 회의를 진행하고 최종 정리를 맡는 |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
한국어 조사 띄어쓰기를 자연스럽게 다듬어 주세요.
Line 7의 Anthropic 의 Claude Opus 이며는 Anthropic의 Claude Opus이며가 자연스럽습니다.
As per coding guidelines, **/*.md user-facing documentation must keep Korean explanations natural.
🧰 Tools
🪛 LanguageTool
[grammar] ~7-~7: Ensure spelling is correct
Context: ...nAI 의 codex, Google 의 agy(Antigravity), Anthropic 의 Claude Opus 이며, 회의를 진행하고 최종 정리를 맡는 의장...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/spec/2026-07-29-council.md at line 7, Update the Korean spacing in
the council description so “Anthropic 의 Claude Opus 이며” becomes “Anthropic의
Claude Opus이며,” while preserving the surrounding meaning and wording.
Source: Coding guidelines
CodeRabbit 4건 + Codex P1 2건. 5건 적용, 1건 skip. TTL 이 자기가 지배하는 파일을 읽고 있었다: - ttl_days 를 레지스트리에서 읽어 만료 창을 정했다. 손으로 3650 을 넣으면 핀이 영영 확인 대상이 되지 않는다. 미래 타임스탬프는 NOW-CHECKED 가 음수라 항상 fresh 였다. "만료되면 항상 질문"은 사용자가 자동 승급을 명시적으로 거부하고 고른 속성인데, 데이터에서 읽는 순간 그 보장이 사라진다. 7일을 상수로 박고 결측·비숫자·미래 타임스탬프를 전부 만료로 처리한다. ttl_days 필드는 지켜지지 않는 설정처럼 보이므로 스키마에서 제거했다. writer 블록이 핀을 복원하지 않았다 (Codex P1): - iter1 에서 좌석 블록에는 재수화를 넣었으나 레지스트리 writer 는 놓쳤다. 문서를 그대로 따라가면 다섯 핀이 전부 빈 문자열인 유효 JSON 이 저장되고 이후 좌석 호출이 전부 빈 모델명으로 실패한다. 리터럴 할당과 빈 값 거부 가드를 넣었다. 같은 지적이 테스트도 짚었다 — 스위트가 핀을 환경변수로 주입해 이 결함을 정확히 가리고 있었다. 주입을 걷어내고 블록이 자립하는지를 검증하도록 바꿨다. codex 부재 경로가 프로브에서 죽었다 (Codex P1): - 실패 정책은 codex 부재를 결석으로 처리하고 나머지 좌석으로 계속한다고 적어 놓고, Step 0 프로브가 그 경로에 닿기 전에 command not found 로 중단시킨다. command -v 로 감쌌다. codex config 쓰기가 성공을 가정했다: - config.toml 이 없는 머신은 [ -f ] 가드에 걸려 블록 전체를 건너뛰어 설정이 영영 쓰이지 않고, mv 실패도 로그만 남기고 지나가 이후 프로브가 통과한다. 없으면 만들고, 쓴 뒤 grep 으로 실제 존재를 검증한다. 테스트: - jq shim 실패 케이스가 종료 상태를 단언하지 않아, shim 이 먹지 않아도 통과할 수 있었다. CR 이 근거로 든 set -e 는 이 스크립트에 없으므로(set -uo pipefail, 실증 확인) 그 전제는 채택하지 않고 nonzero 단언만 추가했다. - 손상된 레지스트리 형태 3종(비숫자·미래·주입된 ttl_days)과 빈 핀 거부를 케이스로 추가. 26 -> 34. skip 1건: 스펙의 조사 띄어쓰기(Anthropic 의 -> Anthropic의). 레포 관례가 반대 방향이다 — 영문 토큰 뒤 조사를 띄운 형태가 AGENTS/README/code_review 에서 148회, 붙인 형태가 20회. 이 파일만 바꾸면 오히려 저장소와 불일치가 된다.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 108541b8ad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ```bash | ||
| if command -v codex >/dev/null 2>&1; then | ||
| codex exec --strict-config -s read-only --skip-git-repo-check \ | ||
| -m "$CODEX_MODEL" -c model_reasoning_effort="\"$CODEX_EFFORT\"" - <<'PROBE' |
There was a problem hiding this comment.
Rehydrate pins before running the strict-config probe
On the initial or expired-registry path, this probe is executed in a separate Bash tool call from the block that assigned the confirmed pins, so CODEX_MODEL and CODEX_EFFORT are empty and the probe fails before the council starts. The new Step 2 rehydration fixes the seat blocks, but it does not cover this earlier probe; reload the registry inside this block as well.
AGENTS.md reference: AGENTS.md:L196-L196
Useful? React with 👍 / 👎.
| the probe below pass with the key still absent, which reads as "configured" when it is not. | ||
|
|
||
| ```bash | ||
| CFG="$HOME/.codex/config.toml" |
There was a problem hiding this comment.
Resolve Codex files through CODEX_HOME
When CODEX_HOME is set, this edits an unused $HOME/.codex/config.toml; the earlier model-cache lookup similarly reads the wrong directory, while the spawned Codex CLI continues using its real home. The repository's existing detector explicitly documents this resolution rule at plugins/project-init/scripts/project_state.sh:259-267, so derive ${CODEX_HOME:-$HOME/.codex} once and use it for both the config and model cache.
Useful? React with 👍 / 👎.
| fi | ||
| ``` | ||
|
|
||
| - `STATE=fresh` — go to Step 1. Ask nothing. |
There was a problem hiding this comment.
Validate fresh pins before skipping registry checks
If a pinned model disappears after a CLI update during the seven-day freshness window, this jump bypasses both candidate-list collection and the Codex probe; agy has no later validity probe at all. The commit's own exception contract in .claude/spec/2026-07-29-council.md:263-270 requires asking for a replacement even before expiry, but the current flow instead reaches Round 1 with a stale model and can merely mark the seat absent.
Useful? React with 👍 / 👎.
| # Shell variables do NOT survive between tool calls — each bash block below is a | ||
| # separate process. Park the run directory where later blocks can re-read it. | ||
| # .claude/state/ is gitignored and machine-local, which is what a run pointer is. | ||
| mkdir -p .claude/state && printf '%s\n' "$DIR" > .claude/state/council-current-run |
There was a problem hiding this comment.
Isolate the active-run pointer per invocation
When two Claude sessions convene councils concurrently in the same repository, the later session overwrites this single shared pointer. Every subsequent Codex and agy block re-reads that path, so the first session begins writing prompts and answers into the second session's tracked decision record, corrupting both councils; key the pointer by session/run identity or carry a run-specific pointer into each block.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugins/council/skills/convene/tests/run-tests.sh (1)
107-109: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
jq변환 실패를 즉시 실패로 처리하세요.
jq ... && mv에서jq가 실패해도 테스트가 계속될 수 있습니다. 특히 Line 128의 변환이 실패하면 기존의 만료된 레지스트리가 남아 Line 129가 거짓 통과합니다. 각 명령을 분리해jq실패 시 즉시 중단되도록 수정하세요.
plugins/council/skills/convene/tests/run-tests.sh#L107-L109:age_registry의jq와mv를 분리하세요.plugins/council/skills/convene/tests/run-tests.sh#L128-L129: 타임스탬프 삭제 변환 실패를 즉시 노출하세요.plugins/council/skills/convene/tests/run-tests.sh#L131-L132: 비숫자 타임스탬프 변환 실패를 즉시 노출하세요.plugins/council/skills/convene/tests/run-tests.sh#L138-L140:ttl_days주입 변환 실패를 즉시 노출하세요.수정 방향
- jq 'del(.checked_at_epoch)' "$REG" > "$T/x" && mv "$T/x" "$REG" + jq 'del(.checked_at_epoch)' "$REG" > "$T/x" + mv "$T/x" "$REG"As per coding guidelines, “파이프라인 중간 명령의 실패나 상태 신호를
|| true등으로 삼키지 않으며”를 준수해야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/council/skills/convene/tests/run-tests.sh` around lines 107 - 109, Ensure every jq transformation fails immediately instead of allowing stale or invalid registry data to pass tests. In plugins/council/skills/convene/tests/run-tests.sh#L107-L109, split jq and mv in age_registry and stop on jq failure; apply the same explicit failure handling to the timestamp-removal transformation at `#L128-L129`, non-numeric timestamp transformation at `#L131-L132`, and ttl_days injection at `#L138-L140`, without swallowing failures via || true or equivalent.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/council/skills/convene/SKILL.md`:
- Around line 47-59: Update the registry validation in the shell flow around
REG, TTL, and checked_at_epoch so STATE=fresh is emitted only when the JSON is
valid and seats contains all required valid seat pins. Treat missing or
malformed seats, missing/non-numeric/future timestamps, and any validation
failure as STATE=expired, ensuring invalid registry data always triggers
confirmation.
- Around line 117-130: Update the registry-writing block in SKILL.md to use the
user-confirmed selections passed as structured data or environment variables
rather than hardcoded model literals. Validate each value against the allowed
candidate lists and safe format before writing, then use jq --arg for JSON
serialization; preserve the refusal to write when any required value is empty
and avoid shell evaluation of user input.
- Around line 176-195: Update the config detection and verification logic around
KEY so it recognizes only a top-level check_for_update_on_startup assignment
appearing before the first TOML table, and requires its value to be true. Do not
accept matching keys inside tables or false values; use the same scoped
validation both before writing and after writing.
- Around line 209-216: Update the Codex probe branch to capture the final
response with the required -o output file, and distinguish a non-zero codex exec
result from the codex-absent case. In the command flow around the codex
availability check, explicitly mark the seat as failed or terminate immediately
when the probe fails instead of continuing as absent; preserve the existing
absent-seat handling only when the codex executable is unavailable.
---
Outside diff comments:
In `@plugins/council/skills/convene/tests/run-tests.sh`:
- Around line 107-109: Ensure every jq transformation fails immediately instead
of allowing stale or invalid registry data to pass tests. In
plugins/council/skills/convene/tests/run-tests.sh#L107-L109, split jq and mv in
age_registry and stop on jq failure; apply the same explicit failure handling to
the timestamp-removal transformation at `#L128-L129`, non-numeric timestamp
transformation at `#L131-L132`, and ttl_days injection at `#L138-L140`, without
swallowing failures via || true or equivalent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 84db96d0-574e-4a90-a36e-e0b14bdf1641
📒 Files selected for processing (2)
plugins/council/skills/convene/SKILL.mdplugins/council/skills/convene/tests/run-tests.sh
CodeRabbit 5건 + Codex P1 1건 + P2 3건. 8건 적용, 1건 skip.
확정 핀이 셸 소스를 통과하지 않게 했다:
- iter2 에서 "변수가 유지되지 않는다"를 리터럴 하드코딩으로 막았는데, 그러면
사용자가 고른 모델이 버려진다. 반대로 사용자 답을 따옴표 문자열에 치환하면
$() 와 따옴표가 셸에 넘어간다. 두 실패 모드가 같은 줄의 양쪽에 있었다.
확정 핀을 Write 도구로 .claude/state/council-pins.json 에 적고, writer 가
그 파일을 읽어 jq --arg 로 넘긴다. 셸이 사용자 값을 파싱하는 지점이 없다.
charset 게이트를 더해 명령 치환과 따옴표 주입이 거부되는 것을 테스트로 확인.
fresh 가 유효를 뜻하지 않았다:
- checked_at_epoch 만 보고 fresh 를 판정해, seats 가 없거나 망가져도 통과한 뒤
좌석이 model=null 로 호출됐다. JSON 파싱과 세 좌석 핀 존재를 fresh 조건에 포함.
- CLI 업데이트로 7일 창 안에 모델이 은퇴하면, 스펙 예외표(라인 269)는 만료 전에도
묻는다고 규정하는데 fresh 경로는 바로 Step 1 로 뛰었다. 자기 스펙과 구현의
모순이다. 로컬 목록 대조로 STALE_PIN 을 검출한다. 목록을 못 읽은 것은 핀이
사라졌다는 증거가 아니므로 그 경우엔 아무 말도 하지 않는다.
CODEX_HOME 을 존중한다 (Codex P2):
- 설정된 머신에서 $HOME/.codex 는 실행 중인 CLI 가 읽지 않는 디렉터리다.
config.toml 과 models_cache.json 양쪽 모두 ${CODEX_HOME:-$HOME/.codex} 경유로
바꿨다. 같은 규칙과 이유가 이 레포의 project_state.sh:259-267 에 이미 문서화돼
있고, Codex 가 그 위치를 정확히 인용했다.
TOML 범위 인식:
- grep 이 [projects."..."] 하위의 같은 키도 매칭하고 = false 도 통과시켜, 실효
설정이 없는데 성공으로 판정했다. 첫 테이블 이전의 top-level true 만 인정한다.
- 그 awk 의 첫 판에 awk 고전 함정이 있었다 — 본문 규칙의 exit 0 이 END 로 점프하고
END 의 exit 1 이 상태를 덮어써, 올바르게 설정된 파일도 항상 거부됐다. 상태를
END 한 곳에서만 정하도록 고쳤다. 이 결함을 스위트가 못 잡은 이유는 awk 의
존재만 grep 하고 실행하지 않았기 때문이라, 설정 블록도 추출해 실행하는
케이스 5종을 추가했다.
프로브가 결과를 안 봤다:
- codex exec 의 종료 상태를 아무도 검사하지 않아 검사가 아니었다. 부재(binary
없음)와 실패(모델/설정 거부)를 구분해 분기한다. 프로브 블록도 레지스트리에서
핀을 다시 읽는다 — iter2 의 재수화가 좌석 블록만 덮고 여기는 빠져 있었다.
세션 격리 (Codex P2):
- 같은 레포에서 두 세션이 동시에 council 을 열면 공유 실행 포인터를 덮어써,
먼저 시작한 쪽이 나중 세션의 git 추적 결정 기록에 프롬프트와 답을 쓴다.
포인터를 세션 키로 나눴다. CLAUDE_SESSION_ID 는 이 환경에 실제로 없어서
(CODEX_COMPANION_SESSION_ID 만 존재) 폴백 없이는 격리가 무효였다.
테스트: 서수 기반 fence 추출을 내용 앵커 기반으로 교체. Step 0 에 블록을 하나
추가하자 서수가 어긋나 엉뚱한 블록을 검사하던 것을 스위트가 스스로 잡았다.
34 -> 49 케이스. 새 불변식은 전부 음성 대조했다.
skip 1건: 조사 띄어쓰기. iter2 와 같은 이유 — 레포 관례가 반대 방향이다.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4626fdcb83
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| mkdir -p "$CODEX_DIR"; [ -f "$CFG" ] || : > "$CFG" | ||
| if ! top_level_true "$CFG"; then |
There was a problem hiding this comment.
Preserve an existing false Codex update setting
When config.toml already contains a top-level check_for_update_on_startup = false, top_level_true returns nonzero and this branch inserts a second assignment instead of recognizing that the user already set the key. The resulting duplicate TOML key is left in the global Codex config even though the post-write verification aborts, potentially breaking subsequent Codex invocations; distinguish an absent key from an explicitly false one and preserve or explicitly replace the existing assignment.
Useful? React with 👍 / 👎.
| elif ! jq -e '(.seats.codex.model // "") != "" and (.seats.codex.effort // "") != "" | ||
| and (.seats.agy.model // "") != "" and (.seats.claude.model // "") != ""' \ | ||
| "$REG" >/dev/null 2>&1; then |
There was a problem hiding this comment.
Expire registries missing the Codex service tier
A recent registry missing only .seats.codex.service_tier passes this freshness validation even though the writer treats that field as required. The new Round 1 rehydration then reads it as null and unconditionally passes service_tier="null" to Codex, turning a recoverable malformed registry into an absent Codex seat instead of routing through pin confirmation; include the tier in the required-pin predicate.
Useful? React with 👍 / 👎.
| if [ -f "$CODEX_DIR/models_cache.json" ] \ | ||
| && ! jq -e --arg m "$CM" 'any(.models[]; .slug == $m)' "$CODEX_DIR/models_cache.json" >/dev/null 2>&1; then | ||
| echo "STALE_PIN=codex:$CM" |
There was a problem hiding this comment.
Distinguish an unreadable cache from a retired Codex model
If models_cache.json exists but is temporarily truncated, malformed, or has an unexpected schema, jq exits nonzero and the negation reports STALE_PIN=codex exactly as if the model were conclusively absent. This sends the user through replacement selection without a trustworthy candidate list, despite the workflow's stated rule that an unreadable list is not evidence of retirement; validate/read the cache successfully first, then test membership separately.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugins/council/skills/convene/SKILL.md (1)
193-205: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win임시 파일을 대상 디렉터리 안에서 생성해야 원자 교체가 보장됩니다.
레지스트리와 Codex 설정의 임시 파일이
/tmp에 생성된 뒤 각각 홈 디렉터리로 이동됩니다. 파일시스템이 다르면mv가 복사 후 삭제로 동작할 수 있어, 중단 시 레지스트리나 설정이 부분적으로 기록될 위험이 있습니다.각 대상 디렉터리 안에서
mktemp -d로 임시 디렉터리를 만들고 그 안에서 생성한 파일을 최종 경로로 교체하세요. 성공·실패 양쪽에서 임시 디렉터리도 정리해야 합니다.Also applies to: 269-275
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/council/skills/convene/SKILL.md` around lines 193 - 205, Update the registry generation flow around tmp and REG to create the temporary directory inside REG’s parent directory, then write the temporary file there before atomically replacing REG. Apply the same-directory temporary-directory approach to the Codex configuration update path. Ensure cleanup runs on both successful replacement and any failure, including removal of the temporary directories.
♻️ Duplicate comments (1)
plugins/council/skills/convene/SKILL.md (1)
65-69: 🗄️ Data Integrity & Integration | 🟠 Major좌석 핀의 타입까지 검증해야 합니다.
현재 조건은 값이 비어 있지 않은지만 확인하므로 객체·배열·숫자·불리언도
fresh판정을 통과할 수 있습니다. 이후 값이 모델명/effort 문자열로 사용되어 잘못된 레지스트리가 사용자 확인을 우회하거나 CLI 호출을 깨뜨립니다.각 필드에 대해
type == "string"및length > 0을 함께 검증하세요. 이전 리뷰의 좌석 구조 검증 문제가 아직 완전히 해결되지 않았습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/council/skills/convene/SKILL.md` around lines 65 - 69, Update the jq validation in the seat-pin freshness check to require every relevant field—codex.model, codex.effort, agy.model, and claude.model—to have type "string" and length greater than zero. Preserve the existing expired outcome for invalid, missing, or unparseable registry data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/council/skills/convene/SKILL.md`:
- Around line 104-110: Separate candidate-list retrieval and validation failures
from stale-pin detection in the Codex and Agy checks. For Codex, require
successful JSON parsing and valid model-list structure before comparing the pin;
for Agy, invoke `agy models` once, capture its successful output in a temporary
file, and only run the pin comparison when retrieval succeeded. Emit `STALE_PIN`
exclusively for successfully read, valid lists that do not contain `CM` or `AM`.
- Around line 346-350: Update RUN_KEY handling before the council state-file
write: require a runtime-provided CLAUDE_SESSION_ID or
CODEX_COMPANION_SESSION_ID, reject missing or invalid values, and validate them
against the [A-Za-z0-9._-] allowlist before using them in the filename. Remove
the shared fallback so concurrent runs cannot reuse the same pointer, while
preserving the existing .claude/state output location.
---
Outside diff comments:
In `@plugins/council/skills/convene/SKILL.md`:
- Around line 193-205: Update the registry generation flow around tmp and REG to
create the temporary directory inside REG’s parent directory, then write the
temporary file there before atomically replacing REG. Apply the same-directory
temporary-directory approach to the Codex configuration update path. Ensure
cleanup runs on both successful replacement and any failure, including removal
of the temporary directories.
---
Duplicate comments:
In `@plugins/council/skills/convene/SKILL.md`:
- Around line 65-69: Update the jq validation in the seat-pin freshness check to
require every relevant field—codex.model, codex.effort, agy.model, and
claude.model—to have type "string" and length greater than zero. Preserve the
existing expired outcome for invalid, missing, or unparseable registry data.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 46516049-1e10-4c13-bcf6-17a94880d843
📒 Files selected for processing (2)
plugins/council/skills/convene/SKILL.mdplugins/council/skills/convene/tests/run-tests.sh
CodeRabbit 3건 + Codex P1 1건 + P2 2건. 5건 적용, 1건 skip. 목록을 읽지 못한 것을 핀 은퇴로 오인했다 (CR·Codex 가 각각 지적): - STALE_PIN 검사가 목록 읽기와 멤버십 판정을 하나의 부정 명령에 뭉쳐 놓아, 잘린 캐시나 실패한 agy 호출이 "모델이 사라졌다"와 구별되지 않았다. 일시적 오류가 사용자에게 불필요한 모델 교체를 요구한다. agy 는 두 번 호출하고 있어서 두 번째 실패도 같은 결론으로 흘렀다. - 바로 아래 산문에 "읽지 못한 목록은 증거가 아니다"라고 써 놓고 정확히 그 반대로 구현했다. 목록을 한 번만 읽어 변수에 담고, 성공한 읽기만 핀을 고발할 수 있게 했다. LIST_UNREAD 를 별도 신호로 분리한다. 사용자가 일부러 둔 false 를 보존한다 (Codex P1): - top_level_true 가 실패하면 곧장 삽입 분기로 갔다. 사용자가 의도적으로 false 를 둔 config 에 두 번째 할당이 얹혀 중복 TOML 키가 되고, 그건 전역 codex config 를 망가뜨린 채 남는다 — 뒤따르는 검증이 abort 해도 파일은 이미 고쳐진 뒤다. absent 와 present-but-not-true 를 구분해, 후자는 보고만 하고 손대지 않는다. 중복 키가 생기지 않는지 카운트로 검증한다. RUN_KEY 도 경로 입력이다: - SLUG 에는 적용한 검증을 세션 키에는 적용하지 않았다. `/` 나 `..` 가 든 세션 id 는 포인터를 .claude/state 밖에 쓰고, 읽을 때는 임의 파일 내용이 DIR 로 들어와 그 경로가 쓰기 대상이 된다. 4개 fence 전부에 charset 게이트를 넣었다. - 세션 id 가 없을 때 shared 로 조용히 충돌하던 것을 명시적 거부로 바꿨다. service_tier 가 필수 검증에서 빠져 있었다 (Codex P2): - writer 는 요구하고 좌석은 읽는데 fresh 판정만 보지 않아, 그 필드만 없는 레지스트리가 통과한 뒤 service_tier=null 이 codex 에 전달됐다. 테스트: - 어서션이 문자열 존재만 보면 한 곳이 게이트를 잃어도 다른 곳의 사본이 초록을 유지한다. RUN_KEY 게이트는 fence 단위 커버리지로 검사하도록 바꿨다 — 부분 제거를 실제로 잡는 것까지 확인했다. - 편집 중 fence 하나가 닫히지 않아 산문이 코드 블록에 들어갔고, 추출기가 그걸 스크립트로 넘겼다. fence 균형과 코드 블록 내 산문 혼입을 검사 항목으로 추가. - 49 -> 58 케이스. 새 불변식은 전부 음성 대조했다. skip 1건: 조사 띄어쓰기. 3회째 같은 지적이고 이유도 같다 — 레포 관례가 반대다.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f23eb8963d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| BASE=".council/$(date -u +%Y-%m-%d)-$SLUG"; DIR="$BASE"; n=2 | ||
| while [ -e "$DIR" ]; do DIR="$BASE-$n"; n=$((n+1)); done | ||
| mkdir -p "$DIR" |
There was a problem hiding this comment.
Allocate each council directory atomically
The pointer is now session-keyed, but when two sessions start the same slug on the same UTC date, both can pass the [ -e "$DIR" ] check before either reaches mkdir -p; that command then succeeds for both and both pointers reference the same directory, so prompts and model answers overwrite one another. Make directory creation itself the loop condition with a non--p mkdir, retrying with the next suffix when it fails.
Useful? React with 👍 / 👎.
| if [ -f "$PTR" ] && [ -d "$(cat "$PTR")" ]; then | ||
| echo "council: another run is already active at $(cat "$PTR") and this runtime exposes no" >&2 | ||
| echo "session id to separate them. Finish that council, or remove $PTR to reclaim it." >&2 | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
Clear the shared run pointer when the council finishes
When neither session-id environment variable is available, a completed run leaves this pointer referencing its still-existing, intentionally git-tracked output directory. No later step removes it, so every subsequent invocation takes this branch and refuses to start until the user manually deletes the pointer; even the message's suggestion to “finish that council” cannot clear the condition. Remove the shared pointer during terminal success/failure cleanup or record an explicit active state separately.
Useful? React with 👍 / 👎.
| elif codex exec --strict-config -s read-only --skip-git-repo-check \ | ||
| -m "$CODEX_MODEL" -c model_reasoning_effort="\"$CODEX_EFFORT\"" \ | ||
| -o "$out" - <<'PROBE' |
There was a problem hiding this comment.
Validate the service tier in the Codex probe
When a pinned service tier becomes invalid while the model and effort remain accepted, this probe reports SEAT=codex OK because it does not pass the tier at all, but the real Round 1 invocation adds -c service_tier=... at line 482 and can then fail. Include the registry tier in the strict probe so the advertised preflight covers the same configuration as the actual seat call.
Useful? React with 👍 / 👎.
| for k in codex.model codex.effort codex.service_tier agy.model claude.model; do | ||
| v=$(jq -r ".$k // empty" "$PINS") | ||
| case "$v" in | ||
| ''|*[!a-zA-Z0-9._-]*) | ||
| echo "council: pin $k is empty or has characters outside [a-zA-Z0-9._-]: '$v'" >&2; exit 1 ;; | ||
| esac | ||
| done |
There was a problem hiding this comment.
Restrict the Claude pin to supported Agent models
During the weekly confirmation, any nonempty charset-safe Claude value is accepted even though the later Agent invocation documents only sonnet, opus, haiku, and fable as valid model values. Unlike Codex and agy, no Claude candidate list is collected either, so a user-entered value such as claude-foo is written successfully and only fails when the seat is launched. Validate claude.model against the Agent model enum before replacing the registry.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/council/skills/convene/SKILL.md`:
- Around line 387-407: Replace the missing-session-ID check-then-write logic in
the convene flow with a mandatory runtime session ID, or an atomic
exclusive-lock claim if lock-based coordination is supported; do not use the
council-run-shared fallback. Ensure the selected pointer/lock claim is atomic,
remove shared-fallback handling from the later re-hydration logic, and make the
pointer write fail the run when it cannot be completed.
In `@plugins/council/skills/convene/tests/run-tests.sh`:
- Around line 47-53: Broaden the prose detection in the “no markdown prose
inside a bash fence” assertion within the test script so it catches headings,
bullets, plain prose, and non-ASCII text rather than only the current
bold-uppercase or quoted pattern. Reuse the existing SKILL fence parsing flow
and ensure the check rejects any Markdown fixture content that could be passed
to Bash as script.
- Around line 79-99: Update the “every RUN_KEY fence validates the id” awk
assertion to detect the actual case "$RUN_KEY" validation guard, rather than any
occurrence of A-Za-z0-9._-. Require that guard to appear before the first
council-run-$RUN_KEY use in each bash fence, while preserving the existing
handling for fences without such uses.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 7d2e5431-f1b0-4a75-ba32-1186b5387434
📒 Files selected for processing (2)
plugins/council/skills/convene/SKILL.mdplugins/council/skills/convene/tests/run-tests.sh
CodeRabbit 4건 + Codex P2 4건. 7건 적용, 1건 skip. 경쟁 상태 3종: - shared 실행 포인터가 check-then-write 였다. 세션 id 없는 두 실행이 모두 "비어있음"을 관찰한 뒤 둘 다 쓴다. mkdir 은 원자적이라 정확히 하나만 성공하므로 그것을 락으로 삼았다. 포인터 쓰기 실패도 이제 검사한다. - 실행 디렉터리도 같은 문제였다. 같은 날짜와 슬러그로 시작한 두 세션이 모두 [ -e ] 를 통과하고, mkdir -p 는 기존 디렉터리에도 성공해 둘이 하나를 공유한다. 비 -p mkdir 을 루프 조건으로 삼아 생성 자체가 중재하게 했다. - 완료된 실행이 shared 락을 남겨 이후 모든 호출이 영구히 거부됐다. 안내 문구가 제안하는 "그 council 을 끝내라"로는 풀 수 없다 — 이미 끝났기 때문이다. Step 5 종료 경로에서 해제한다. 프로브가 실제 호출과 다른 설정을 검증했다: - service_tier 를 넘기지 않아, 모델과 effort 는 유효하고 tier 만 무효인 핀이 프로브를 통과한 뒤 Round 1 에서 실패한다. preflight 가 존재하는 이유가 바로 그 경우다. 이제 좌석 호출과 동일한 설정으로 프로브한다. Claude 핀만 후보 검증이 없었다: - codex 와 agy 는 CLI 목록과 대조하는데 Claude 좌석은 charset 만 통과하면 무엇이든 기록됐다. claude-opus-4 같은 그럴듯한 값이 좌석 기동 시점에야 실패한다. Agent 도구가 받는 네 값으로 검증한다. 테스트 어서션 두 개가 약했다 (둘 다 내가 직전 회차에 "고친" 것): - 산문 탐지가 ** 대문자 패턴만 잡아 한글·불릿·헤딩이 fence 안에 남을 수 있었다. 패턴 추격은 이길 수 없는 게임이라 bash -n 으로 각 fence 를 실제 파싱한다. 산문 유출과 구문 오류를 함께 잡고, 산문이 어떻게 생겼는지 추측하지 않는다. base64 -w0 은 GNU 전용이라 임시 파일 분할로 구현했다 (macOS 레그 동일 실행). - RUN_KEY 게이트 검사가 charset 문자열의 등장만 보아, 주석에 그 문자열이 있으면 실제 case 가드가 사라져도 초록이었다. 가드 구문 자체를 추적하고 첫 사용보다 앞에 있을 것을 요구한다. 58 -> 65 케이스. 새 불변식 5종 전부 음성 대조했다. skip 1건: 조사 띄어쓰기, 4회째. 이유 동일 — 레포 관례가 반대다.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9070bee7c8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| First record the confirmed answer with the **`Write` tool** (not a heredoc, not `echo`) to | ||
| `.claude/state/council-pins.json`. Nothing in that path is parsed by a shell, so no value the | ||
| user typed can become a command: |
There was a problem hiding this comment.
Create the state directory before writing confirmed pins
When the council is first invoked in a repository without .claude/state/, the Write tool cannot create this file because its parent directory does not exist. The only mkdir -p .claude/state appears later in Step 1, so the missing/expired-registry path fails before it can create the registry; create the directory before this Write step or stage the pins under an already-existing global directory.
Useful? React with 👍 / 👎.
| elif ! jq -e '(.seats.codex.model // "") != "" and (.seats.codex.effort // "") != "" | ||
| and (.seats.codex.service_tier // "") != "" | ||
| and (.seats.agy.model // "") != "" and (.seats.claude.model // "") != ""' \ | ||
| "$REG" >/dev/null 2>&1; then |
There was a problem hiding this comment.
Validate registry pin types and the Claude model enum
When a recent registry was hand-edited, produced by an older version, or otherwise corrupted, this predicate accepts any nonempty value rather than the documented pin shapes; for example, claude.model: "claude-opus-4" still yields STATE=fresh. The fresh-path validity checks only query Codex and agy, so the invalid Claude pin reaches the Agent call and turns a recoverable registry problem into an absent seat. Apply the same string/charset and Claude-enum validation used by the writer before declaring the registry fresh.
Useful? React with 👍 / 👎.
PR #189 (council 플러그인) 의 post-merge 통합. cr-fix 5회차에서 나온 39건 중 설정으로 갈 것과 lore 로 갈 것을 나눠 각각 한 곳에만 기록한다. 설정 (기계적 규칙): - README 의 "CI 가드가 지키는 것" 절이 총계를 산문으로 적는데 check-doc-consistency 는 README 총계를 `(\d+) / (\d+) 플러그인` 형태로만 매칭한다. 그 문장은 숫자 뒤에 "플러그인" 이 없어서 가드가 자기 자신을 설명하는 문장이 stale 해도 통과한다. 숫자를 틀리게 바꿔 실증했다. plugin-versioning 규칙과 AGENTS.md 미러 양쪽에 카운트-홈으로 추가. wiki (lore): - testing-shell-embedded-in-docs: 셸이 SKILL.md 안에 사는 스킬은 테스트할 실행 파일이 없다. 테스트 파일에 로직을 옮겨 적으면 원본이 썩는 동안 스위트는 영원히 초록이다. fence 를 추출해 throwaway HOME/cwd 에서 돌린다. 추출은 서수가 아니라 내용으로 앵커한다 — fence 하나를 앞에 넣자 서수가 엉뚱한 블록으로 미끄러졌고 스위트가 그걸 스스로 잡았다. 구문의 존재를 grep 하는 것은 테스트가 아니다 — awk 의 exit 가 END 에 덮여 항상 실패하던 헬퍼가 존재 grep 을 4회차 동안 통과했다. 산문 유출은 패턴으로 못 잡는다 (한글·불릿·헤딩이 다 샌다); bash -n 이 실제 파서다. - council-cross-vendor-design: 의장과 같은 모델인 좌석의 동의는 독립된 두 번째 판단이 아니라 3표 중 2표라는 착시를 만든다. 동족 합의 할인 + 적대 역할로 갚되, 라운드 2 가 공격 과제라 좌석은 여전히 opus. 정책 TTL 은 코드 상수여야 하고 그것이 지배하는 파일에서 읽으면 보장이 아니다. fresh 는 valid 가 아니며, 읽지 못한 목록은 핀이 사라졌다는 증거가 아니다. 확정 핀은 Write 도구로 쓴 JSON 으로 전달하고 셸 소스를 거치지 않는다. 환경 사실: CLAUDE_SESSION_ID 는 export 되지 않는다. - detector-cannot-look Mode 10 에 범위 변형 추가: 어서션이 문서 전체를 검색하면 형제 사본이 통과시키고, 블록 단위로 좁혀도 주석의 문자열이 게이트로 계수된다. 두 번의 수정이 모두 같은 축(패턴)을 조였다가 실패했다. 범위를 좁히고, 구문 자체를, 그리고 그 위치를 단언해야 잡힌다. 인사이트 레이어에도 "looked, and let go" 로 미러. evidence_count 7->8. TOCTOU 자체는 교과서 지식이라 기록하지 않는다. README 카운트-홈은 설정으로 갔으므로 wiki 에 중복 기록하지 않는다.
Closes #188
무엇을
/council:convene— 하나의 질문을 서로 다른 벤더의 세 모델에게 던지고, 그 셋이 서로의 답을읽고 반박하게 한 뒤, 합의된 것·끝내 갈린 것·아무도 답 못한 것을 문서로 남긴다.
설계 전문은
.claude/spec/2026-07-29-council.md에 있다 (인터뷰 3라운드로 확정).왜
Claude 서브에이전트를 여러 개 띄우는 것은 관점을 늘리지 못한다. 가중치를 공유하므로 같은
방식으로 틀린다.
core-config가 이미[council]힌트 줄을 매 프롬프트에 주입하고 있었으나위임을 실제로 실행하는 경로는 없어서, 매번 손으로 명령을 조립해야 했다.
구조
의장은 메인 세션이고 의석이 아니다. 프롬프트를 짜고, 사용자 답을 중계하고, 합성한다.
codex execgpt-5.6-sol/ effortxhigh/ tierfastagy --printgemini-3.6-flash-highAgent도구 model 오버라이드opus라운드1 독립 의견 → 재질문 관문(각 좌석이 낸 확인 요청을 의장이 병합해 사용자에게 한 번) →
라운드2 상호 반박 → 합성.
설계상 눈여겨볼 두 가지
Claude 의석이 의장과 같은 모델이다. 세션이 갈려도 가중치는 안 갈리므로, 이 좌석의 동의는
독립된 두 번째 판단이 아니라 같은 편향의 반복일 수 있다. 그대로 두면 의장이 "3표 중 2표가 내
편"이라고 잘못 읽는다. 동족 합의 할인(같은 결론이라는 사실만으로는 계수하지 않고, 의장이
쓰지 않은 새 논거를 가져왔을 때만 가산)과 적대 역할 부여로 갚는다. 그럼에도
opus인 이유는라운드2가 공격 과제라서다 — 약한 좌석은 약한 반박을 내고, 약한 반박은 의장이 걸러 기권이 된다.
만료 시 자동 승급하지 않는다. 양쪽 CLI 모두 모델 목록이 기계 판독 가능해서(
agy models,~/.codex/models_cache.json) 자동 갱신이 기술적으로 가능하지만, 새 모델이 실제로 나은지의판단은 사용자 것이고 조용한 승급은 의도치 않은 비용으로 흐른다. 만료되면 실제 목록을 근거로
보여주며 항상 묻는다.
실행으로 확인한 사실
행잉 원인이 아니다.
check_for_update_on_startup은 실재하는 codex 설정 항목이다.--strict-config로 통과 /가짜 키(
totally_bogus_key_xyz)는unknown configuration field로 거부되어 양성·음성 대조를마쳤다.
(feat(cr-fix): add over_engineering YAGNI judgment axis #76)은 관측되지 않았으나, 파일 쓰기 우회는 비용이 0 이고 실패했을 때만 손해가 나는
비대칭이라 유지한다.
-)으로 받고 답을-o파일로 낸다. stdout 에는 훅 로그와 토큰 수가섞이므로 파싱하지 않는다. 프롬프트가 셸에서 재파싱되지 않아 인젝션 표면도 사라진다.
테스트
convene은 번들 스크립트가 없어 호출 계약이SKILL.md안에만 있고, 따라서 아무도 실행하지않는다. agy 호출에서
< /dev/null이 빠지면 그 의석이 영원히 멈추는데(--print-timeout도못 막음) GNU 전용 구문이 아니라서
check-shell-portability의 관심사도 아니다.plugins/council/skills/convene/tests/run-tests.sh(17 케이스)를 붙이고 pre-commit + CI(GNU·macOS
/bin/bash3.2 양쪽)에 배선했다 — 배선하지 않은 테스트는 장식이다. 계약을일부러 깨뜨려 실제로 실패하는 것까지 확인했다.
이식성 중복 검사는 일부러 뺐다.
check-shell-portability.mjs가 코드와 산문을 이미 제대로구분하는데, grep 으로 재구현하면 이 파일 자신의 설명을 오탐한다 (초안에서 실제로 그랬다).
Codex 제외
CODEX_EXCLUDED에 넣었다. Codex 에서 돌리면 codex 가 자기 자신을 의석으로 소환하는 순환이고,Claude 의석이 필요로 하는
Agent도구가 Codex 에는 없다.codex-image와 같은 논리다.플러그인 총수는 24 → 25 이지만 Codex-eligible 은 23 그대로다 (1 추가 + 1 제외).
의도적으로 뺀 것
자율 토론 모드(agent teams). 기술적으로 가능함은 확인했다 —
SendMessage수신자가 팀원 이름을직접 받고, 공식 문서가 바로 이 용례를 예시로 싣는다. 뺀 이유는 팀원이 사용자에게 질문할 수
없어 재질문 관문과 충돌해서다. 부수적으로 기존 rescue 정의를 좌석으로 재사용할 수 없고
(본문에 "한 번만 전달하고 후속 작업 금지"가 박혀 있음) 종료 보장 장치가 따로 필요하다. 조사
결과는 스펙의 "보류된 갈래" 절에 남겨 다시 꺼낼 때 처음부터 조사하지 않게 했다.
검증
전부 로컬 실행 통과:
sync-codex-manifests --checksync-hermes-manifests --checkcheck-doc-consistencycheck-skill-tool-portability --checkcheck-shell-portabilitymock-load-hermescheck-skill-prose경고 22건은 전부 기존 플러그인(ml-toolkit, rules-forge 등) 것이고 council발 신규는 없다.
미확인
못했다. 이 PR 은
check_for_update_on_startup을 명시적으로 기록하는 것까지만 다루고 행잉을고친다고 주장하지 않는다.
실측해 조정해야 한다.
model: "opus"서브에이전트 실사용. 도구 스키마상 유효함만 확인했다.Summary by CodeRabbit
New Features
councilplugin with the/council:convenemulti-round cross-model workflow (independent opinions, user confirmation gate, rebuttals, and synthesized outcomes).councilas Claude-only (excluded from Codex sync).Documentation
councilspecs, including output locations and portability/runner guidance.Tests
convenetest execution to both local commit checks and CI validation.