Rubber duck review
Installable agent skills and plugin-ready workflows for coding agents
npx -y skills add Vivswan/skills --skill rubber-duck-reviewAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Cross-model code review using a second agent, tool, or read-only CLI fallback. This skill should be used when someone asks to rubber duck a change, get a second opinion, or run an independent review focused on correctness, future-proofing, and design quality.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
15.7 KB, as published. Nobody here has run it
Rubber Duck Review
Get a second opinion on code changes from a different model while you keep ownership of the task.
When to Apply
Use this skill when someone asks for:
/rubber-duck-review- "rubber duck my changes"
- "get a second opinion on this"
- "review this with another model"
Workflow
1. Pick the reviewer
- Prefer a dedicated review tool when one is available.
- Use a reviewer that is different from the current model when possible.
- If no dedicated tool exists, fall back to a read-only CLI invocation. Pick by which model you are, then use the matching full pattern in step 3:
- If you are currently using Claude → Codex reviewer (§3a):
codex exec --json --sandbox read-only, fallbackcopilot -p --deny-tool='write' --deny-tool='shell'. - If you are currently using Codex or GitHub Copilot → Claude reviewer (§3b):
claude -p --permission-mode plan --verbose --output-format stream-json, fallbackcopilot -p .... - Always pass the prompt as
"$(cat "$prompt_file")"(a file inside the scratch tmp dir, see §3) and append< /dev/null; the copy-paste blocks in §3a/§3b already do both.
- If you are currently using Claude → Codex reviewer (§3a):
- Never let the reviewer write files, edit code, or run unrestricted shell commands.
--sandbox read-only(codex) /--permission-mode plan(claude) lets it run read-only commands (grep,git diff, typecheck) but blocks writes. That self-checking makes findings concrete. (Note: a read-only sandbox can block temp-dir creation, so the reviewer may skip tests that need to write.)
2. Craft the prompt
- Ask for a review of the relevant changes and surrounding context only.
- Always include:
This is a review-only task. Do not edit, write, or modify any files. Only read and report findings. - Tell it how to see the change (e.g. "run
git --no-pager diff HEADand read the new files") rather than pasting diffs; it follows imports and cross-file behavior better that way. - Ask the reviewer to look for:
- Correctness issues
- Future-proofing risks
- Naming or design choices that will become awkward as the codebase grows
- Hardcoded assumptions that may become misleading later
- Workarounds propped up by long justification comments: if it takes a paragraph-long comment to argue the workaround is OK, the code is wrong. Flag both the comment and the code for fixing.
- Fold in criteria from companion skills. For each skill in the list below that is installed, expand its
## Review Criteriasection into the reviewer prompt and triage the resulting findings with that skill's own workflow. To make another skill part of the review later, add its name here:no-invalid-states
- Ask it to report prioritized findings (blocking vs non-blocking) and to say so plainly if the code is correct; this keeps re-reviews terminable.
- If the user has already declined or reverted something in this thread, add a short
Already decided / out of scopesection so the reviewer does not keep re-raising it. - On a re-review, state which fixes were already applied so it focuses on what remains.
For a reusable prompt template, see references/reviewer-prompt.md.
3. Run the reviewer, with streaming progress
General principles (apply to both reviewers in 3a/3b below):
-
Put every artifact in a throwaway scratch tmp dir, never the working tree. Create one per review and derive all paths from it:
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/rubber-duck.XXXXXX")" prompt_file="$tmp_dir/prompt.txt"; out_file="$tmp_dir/review.jsonl"; err_file="$tmp_dir/review.err"Write the crafted prompt into
$prompt_file(heredoc with a quoted delimiter, or your Write tool). Keepingprompt.txt/review.jsonl/review.errin a tmp dir means they can't be staged or committed by accident, and cleanup is a singlerm -rf "$tmp_dir"once you've extracted the verdict. Run the launch + watch + wait sequence in one shell invocation so$tmp_dirstays in scope; shell variables don't survive across separate tool calls. For fan-out (step 5), give each section its own file names inside the dir (e.g.$tmp_dir/review.api.jsonl). -
Launch the review in the background and capture output to a file; keep working while it runs. Read findings only when you need them to decide the next step.
-
Redirect stdin from
/dev/null(< /dev/null). Bothcodex execandclaude -pread stdin for additional input on top of the prompt arg. When backgrounded, or run in a non-interactive tool shell with no TTY, they block waiting on stdin that never closes: codex printsReading additional input from stdin...and emits zero JSON events (a silent hang that looks identical to "still thinking"). Always append< /dev/nullso the prompt argument is the only input. This is the single most common cause of a review that never starts. -
Keep the launching shell alive until every reviewer exits. In short-lived tool shells, a backgrounded
claude,codex, orcopilotprocess can be reaped when the shell exits, leaving zero-byte stdout/stderr files and no error. Start the reviewer, store its PID, run the progress watch, andwaitfor that PID in the same shell invocation. Do not launch the process in one tool call and poll it from another unless the process has been detached with a mechanism you have verified in this environment. If your tool shell can't reliably keep a backgrounded process alive, run the reviewer foreground with a timeout instead (same command, drop the&/wait); a clean blocking run beats a reaped background one. -
Always stream JSON (
--jsonfor codex,--output-format stream-json --verbosefor claude). A steadily growing event count means it's reading/reasoning; a file flat for minutes means it's stuck. A single final-object format gives no liveness signal during a long review. Do not treat an output file that is still empty after only a few seconds as failure; a real review can take a while to emit its first events. -
Capture the FULL stream, never a
tail/head/grepsubset of the live pipe. Redirect all of stdout straight to a file and poll that whole file. Piping throughtail/headdefeats liveness detection (the pipe buffers, so you can't tell if events are still arriving) and can truncate the final verdict. Subset only when reading a finished file. -
Don't let the shell touch the prompt. Backticks and
$(...)inside a double-quoted shell argument run as command substitution before the reviewer starts, so a prompt that mentions`tar`or`git diff`silently executes them, mangling the prompt (stray errors liketar: Must specify one of -c, -r, -t...and zero JSON events). Write the prompt to a file in the scratch tmp dir and pass it as"$(cat "$prompt_file")"; substitution results are not re-evaluated, so backticks in the file stay literal. -
A progress watch must emit at most TWO notifications, not one per tick. Emit: (1) once when the streams first show life (event count > 1), and (2) once when every reviewer process has exited. A reviewer that exits before showing life produces only the second. Stay silent otherwise; poll on a sleep loop but only
echoon those two transitions.
3a. Codex reviewer (use when you are Claude)
Read-only via --sandbox read-only (allows grep / git diff / typecheck, blocks writes). Note < /dev/null.
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/rubber-duck.XXXXXX")"
prompt_file="$tmp_dir/prompt.txt"; out_file="$tmp_dir/review.jsonl"; err_file="$tmp_dir/review.err"
cat > "$prompt_file" <<'PROMPT'
# ... your review prompt; see step 2 / references/reviewer-prompt.md ...
PROMPT
touch "$out_file" # pre-create so the progress loop never reads a missing file
codex exec --json --sandbox read-only "$(cat "$prompt_file")" < /dev/null >"$out_file" 2>"$err_file" &
reviewer_pid=$!
# Event 1: first sign of life. Stay quiet until then.
alive_reported=0
while kill -0 "$reviewer_pid" 2>/dev/null; do
line_count="$(wc -l < "$out_file" 2>/dev/null || echo 0)"
if [ "$alive_reported" -eq 0 ] && [ "$line_count" -gt 1 ]; then
echo "review alive"; alive_reported=1
fi
sleep 2
done
# Event 2: reviewer exited. Keep this wait in the launching shell.
wait "$reviewer_pid"; review_status=$?
echo "review complete (status=$review_status)" # verdict in $out_file; rm -rf "$tmp_dir" when done
exit "$review_status"
Foreground fallback (no background management; blocks until done, so give the tool a generous timeout):
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/rubber-duck.XXXXXX")"; prompt_file="$tmp_dir/prompt.txt"
cat > "$prompt_file" <<'PROMPT'
# ... your review prompt ...
PROMPT
codex exec --json --sandbox read-only "$(cat "$prompt_file")" < /dev/null > "$tmp_dir/review.jsonl" 2> "$tmp_dir/review.err"
3b. Claude reviewer (use when you are Codex or GitHub Copilot)
Read-only via --permission-mode plan (no edits/writes). --output-format stream-json requires --verbose; without it Claude errors to stderr and stdout stays empty. Note < /dev/null.
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/rubber-duck.XXXXXX")"
prompt_file="$tmp_dir/prompt.txt"; out_file="$tmp_dir/review.jsonl"; err_file="$tmp_dir/review.err"
cat > "$prompt_file" <<'PROMPT'
# ... your review prompt; see step 2 / references/reviewer-prompt.md ...
PROMPT
touch "$out_file" # pre-create so the progress loop never reads a missing file
claude -p --permission-mode plan --verbose --output-format stream-json "$(cat "$prompt_file")" < /dev/null >"$out_file" 2>"$err_file" &
reviewer_pid=$!
# Event 1: first sign of life. Stay quiet until then.
alive_reported=0
while kill -0 "$reviewer_pid" 2>/dev/null; do
line_count="$(wc -l < "$out_file" 2>/dev/null || echo 0)"
if [ "$alive_reported" -eq 0 ] && [ "$line_count" -gt 1 ]; then
echo "review alive"; alive_reported=1
fi
sleep 2
done
# Event 2: reviewer exited. Keep this wait in the launching shell.
wait "$reviewer_pid"; review_status=$?
echo "review complete (status=$review_status)" # verdict in $out_file; rm -rf "$tmp_dir" when done
exit "$review_status"
Foreground fallback:
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/rubber-duck.XXXXXX")"; prompt_file="$tmp_dir/prompt.txt"
cat > "$prompt_file" <<'PROMPT'
# ... your review prompt ...
PROMPT
claude -p --permission-mode plan --verbose --output-format stream-json "$(cat "$prompt_file")" < /dev/null > "$tmp_dir/review.jsonl" 2> "$tmp_dir/review.err"
copilot -p --deny-tool='write' --deny-tool='shell' "$(cat "$prompt_file")" < /dev/nullis the secondary fallback for either side; it does not stream JSON, so you lose the liveness signal. Prefer codex/claude above.
4. Extract the verdict from JSON streams
-
For Codex JSONL, the final answer is the last
item.completedevent whoseitem.typeisagent_message. Codex emits intermediateagent_messagenarration as it works, so take the last one, not the first match. -
For Claude
--output-format stream-json, the final answer is theresultstring on the final{"type":"result", ...}event. If the stream ended before a result event, use the last{"type":"assistant", "message": ...}text only as a partial/incomplete clue and relaunch if needed. -
Watch for
errorevents (e.g.stream disconnected before completion). If the lastagent_messageis narration rather than a findings list, the stream was cut mid-thought; relaunch that review. -
jq-free extraction:
let msg = null; for (const l of read(file).split("\n").filter(Boolean)) { try { const o = JSON.parse(l); if (o.type === "item.completed" && o.item?.type === "agent_message") msg = o.item.text; if (o.type === "result" && typeof o.result === "string") msg = o.result; } catch {} } // msg = the final review; also scan for {"type":"error"} events. -
Once you've extracted the verdict, remove the scratch dir:
rm -rf "$tmp_dir". On a re-review (step 7), start a freshmktemp -drather than reusing the old dir.
5. Large change sets: fan out one review per section
- A single broad review of a big diff is shallower than several focused ones. Split the change into logical sections (each new command/module, each script, the CI/release config, a parity pair) and run one review per section in parallel, each scoped to its files.
- Don't over-parallelize. Many simultaneous
codex execprocesses can saturate the backend and hang. If reviews stall, run them in smaller batches (2-3 at a time). - Detect & recover from hangs. With JSON streaming, compare each review's event count over ~30-60s. If one is flat while its siblings climb, it's hung: stop it (the task runner's stop, or
pkill -f "<unique substring of that prompt>") and relaunch just that one.
6. Apply findings thoughtfully
- Treat valid "non-blocking" feedback as real work, especially when it points at design traps or future maintenance issues.
- Fix valid non-blocking findings, including ones that improve maintainability (clearer naming, removed duplication, simpler structure), not just the blocking ones. A valid one may be skipped only when the fix would conflict with the design, reach outside the change under review, or go against an explicit user decision; record why. A finding you judge incorrect or inapplicable is not skipped but rejected, per the next bullet.
- Do not blindly accept every finding. If you disagree, explain why, and watch for fixes that would conflict with the design (e.g. a suggested guard that breaks a legitimate path).
- If a finding conflicts with an explicit user decision, follow the user and record that the issue was intentionally skipped.
- Re-validate after each batch of fixes (typecheck / lint / tests) before re-reviewing.
7. Re-review until it converges, before committing
- After applying fixes, re-run the review on the updated state, one review per section, not a single overall pass.
- Repeat until no valid blocking findings remain and every non-blocking finding has been handled per step 6: fixed, skipped for a recorded reason, or rejected as incorrect or inapplicable. Treat that convergence as the gate.
- Review before you commit, not after. Run the review on the working-tree changes, apply/triage findings, then commit, so a flaw can't land (or push) before anyone looks at it. This matters most where commits go straight to the main branch.
8. If these instructions don't work, fix the skill
- If a step in this skill fails in practice and the root cause is the instructions themselves (a documented flag no longer exists, an extraction rule misses the verdict, a copy-paste block breaks in a reproducible way), work around it to finish the current review, but don't stop there: capture the fix so the next run doesn't rediscover it. First rule out transient causes (backend outage, sandbox restriction, local tool config); those are not skill defects.
- This applies to you, the driving agent; the reviewer stays read-only per step 1. Fix the skill only where you can edit its canonical source: in the authoring repo, edit the affected skill sources directly (
SKILL.md,references/, orscripts/). From an installed or vendored copy, or when you're not authorized to write, report the defect and your proposed fix to the user instead. - Fold the root cause into the relevant step rather than appending a one-off note, and keep the copy-paste blocks runnable as written.
- A skill edit is a working-tree change like any other: finish the review with the corrected instructions, and put the edit through the same validate, review, and converge gate (steps 6 and 7) before it is committed.