When OAuth tokens rotate or refresh, all reviewer containers are
automatically removed. The next reviewer turn recreates them with
the latest token, preventing 401 auth failures.
Uses callback pattern to avoid circular dependencies:
- token-rotation.ts: onTokenRotated(cb)
- token-refresh.ts: onTokenRefreshed(cb)
- index.ts: registers recreateAllReviewerContainers as callback
The counter was being reset to 0 on every owner turn, including
bot-only ping-pong turns. This prevented the 3-round-trip echo
loop breaker from ever triggering. Now only human messages reset
the counter.
When both owner and reviewer keep echoing DONE_WITH_CONCERNS without
making progress, the system now stops the ping-pong after 3 round
trips and sets the task to completed, escalating to the user.
Owner was "agreeing" with concerns instead of fixing code. Reviewer
was listing issues without directing the owner to fix them. Now:
- Reviewer: explicitly direct the owner to fix specific issues
- Owner: fix issues when flagged, don't just acknowledge
Both agents were repeating "agreed, 3 concerns remain" without
taking action. Now DONE_WITH_CONCERNS requires listing specific
actions, and repeating the same concerns for 2+ turns triggers
BLOCKED escalation.
Reviewer failover handoffs were routed through the owner channel,
causing codex-review to run in owner mode instead of reviewer mode.
Now detects reviewer handoffs by reason prefix and routes them
through the correct reviewer channel.
The unified service runs all three bots (claude, codex-main,
codex-review) in one process but only queried handoffs for SERVICE_ID
(claude). Reviewer failover handoffs targeting codex-review were
never picked up. Now queries all three service IDs.
Root cause: owner and reviewer shared the same message cursor. When
the owner's turn completed, the cursor advanced past the owner's
message. The reviewer's turn then only saw messages after that cursor,
missing the owner's response entirely.
Now paired rooms use role-aware cursor keys (chatJid for owner,
chatJid:reviewer for reviewer). Each role advances only its own
cursor, so the reviewer always sees the owner's latest messages
and vice versa.
The finalize turn (auto-triggered after reviewer approval) was being
treated as a new working turn because merge_ready was unconditionally
reset to active. Now merge_ready is only reset when a human message
is in the batch, preserving the finalize flow for bot-only turns.
In paired rooms, bot messages now show sender as "오너" or "리뷰어"
instead of the Discord nickname. Uses channel.isOwnMessage() to
identify which bot sent each message, then maps via the channel
lease to determine the role. This ensures agents correctly identify
who is the owner and who is the reviewer regardless of bot nicknames.
Discord file attachments are downloaded to data/attachments/ on the
host. Mount this directory read-only in the reviewer container at the
same absolute path so both owner and reviewer can access uploaded
files (WAV, images, etc.) using the same path.
Voice messages (Discord IsVoiceMessage flag or attachment with duration)
are still transcribed via Whisper. Regular audio file attachments (WAV,
MP3, etc.) are now downloaded to disk and the path is passed to the
agent, matching image attachment behavior.
When the reviewer is Claude and hits auth/429/overloaded errors,
hand off the review turn to codex-review instead of swapping
owner/reviewer roles (which is the owner failover path).
When a user sends a new message while the task is in merge_ready,
review_ready, or in_review status, the owner's turn was incorrectly
treated as a finalize turn. This caused premature task completion
(DONE → completed) even when the user wanted to continue working.
Now resets status to active alongside round_trip_count, ensuring
the owner gets a fresh working turn instead of a finalize turn.
The credential proxy cached the OAuth token at startup, but Anthropic
no longer supports raw OAuth Bearer tokens on the API. The Claude Code
SDK handles OAuth internally, so the proxy was breaking auth.
Now injects the real token both at container creation and at each
docker exec, so token rotation is picked up without recreating the
container.
When the owner registers a CI watcher and completes the turn, the
review loop no longer triggers. The watcher result will be posted
via the reviewer bot, which the owner picks up and acts on, then
the normal review loop resumes.
Git worktrees reference the parent repo's .git directory via absolute
path. Without mounting it, all git commands fail inside the container.
Now reads the .git file, resolves the parent .git path, and mounts it
read-only at the same host path so references resolve naturally.
CLAUDE_CONFIG_DIR included .claude suffix, but the container mounts
the parent directory at /home/node/.claude. This caused double nesting:
/home/node/.claude/.claude/CLAUDE.md — SDK never found the prompts.
All reviewers (claude, codex-review, codex-review-failover) now use
claude-paired-room.md. All owners use owner-common-paired-room.md.
Removed: codex-paired-room.md, codex-review-paired-room.md,
codex-review-failover-paired-room.md, codex-review-platform.md
10 prompt files → 6.
The anti-pattern guardrails ("It should work now" means run it, etc.)
and stagnation pattern names (Spinning/Oscillation/Diminishing/No progress)
are too useful to compress out — they prevent common LLM failure modes.
Container mounted data/sessions/{folder}/ but CLAUDE.md was written
to data/sessions/{folder}-reviewer/.claude/ by paired-execution-context.
The reviewer never saw its prompts (verdict protocol, paired room rules).
Now mounts {folder}-reviewer/ to match, so the CLAUDE.md with platform
and paired room prompts is visible inside the container.
The alreadyVisible check compared progress text with final text and
skipped deliverFinalText when they matched. This caused legitimate
final outputs to vanish when the progress message happened to
accumulate the same text through edits.