GitLab Duo CLI Advanced Patterns — Batch Automation, Multi-Model Comparison & Noise Reduction
My June 2026 Duo CLI batch review workflow, updated October 8 with model identifiers, headless auto-approval, Credits billing, retryable completion markers, and timeout scripts.
Contents
This follows my getting started article and keeps the June 2026 batch review experience. The October 8, 2026 update checks versions, model identifiers, billing, and headless behavior. The local runner logic has offline tests; this refresh did not run tasks that consume Credits.
Why Batch Automation
The original post records a time when I was reviewing a large refactoring MR that touched about a dozen modules. Using Duo Chat on the web, I asked about each module one by one. By the fifth one, I'd already forgotten the conclusions from the first. And each time I had to re-provide context, because Duo Chat didn't know what I'd asked earlier (this was before session sync existed).
After that, I started scripting. I queued up review tasks for all twelve modules, ran them in one go, results saved to files. Made myself a coffee, came back, everything waiting for me.
That's the core value of batch automation: decoupling "waiting for AI" from your attention.
Minimum Viable Batch Setup
October 8, 2026 update: access and execution boundaries
Complete the versions, authentication, and namespace setup in part one. The setup guide currently requires glab 1.107.0 or later. GitLab 19.2 is the generally available starting point; 18.11 through 19.1 need beta and experimental features enabled. Meet the Agent Platform prerequisites, and set a default Duo namespace or launch from a project with Duo access. A script cannot bypass group access requirements.
The headless documentation says glab duo cli run automatically approves tools. It does not need an additional --dangerously-skip-permissions flag. Removing that flag below does not make the batch read-only. Use a disposable working copy and a controlled sandbox, keep production credentials out, and inspect the diff afterwards. A prompt saying “review only” is not a security boundary.
Task data and completion markers
My setup is three directories:
my-review/
├── cells/ # One JSON file per task
├── state/ # Completion markers (.done files)
└── results/ # AI responses land here
A cell looks like this:
{
"id": "auth-module-error-handling",
"target": "/path/to/repo",
"model": "claude_opus_4_6_20260205",
"goal": "Review the auth module's error handling, focusing on token expiry edge cases"
}
The runner script:
#!/bin/bash
for cell_file in cells/*.json; do
cell_id=$(jq -r '.id' "$cell_file")
# Skip completed cells
[ -f "state/${cell_id}.done" ] && continue
goal=$(jq -r '.goal' "$cell_file")
target=$(jq -r '.target' "$cell_file")
model=$(jq -r '.model' "$cell_file")
echo "⏳ Running: $cell_id"
if glab duo cli run \
-C "$target" \
--goal "$goal" \
--model "$model" \
> "results/${cell_id}.md" 2>&1; then
touch "state/${cell_id}.done"
echo "Done: $cell_id"
else
status=$?
echo "Failed: $cell_id (exit $status); retry on the next run" >&2
fi
sleep 30 # avoid rate limits
done
echo "Batch finished."
The key is the .done file: only a successful run creates it. A failed command leaves its output in results/ without a marker, so the next run retries that cell and skips the ones that succeeded.
I sleep 30 seconds between cells. Not strictly required, but hitting the API too fast occasionally triggers rate limits. 30 seconds has been stable for me.
Don't Forget Timeouts
Learned this one the hard way. Some tasks make the AI think forever, especially if you give it a huge repo with a vague question. Once I started a 10-cell batch before bed, woke up to find it stuck on cell 3, running for 6 hours straight.
Now I always add a timeout:
# Run in background + track time
glab duo cli run -C "$target" --goal "$goal" \
--model "$model" \
> "results/${cell_id}.md" 2>&1 &
PID=$!
TIMED_OUT=0
ELAPSED=0
while kill -0 "$PID" 2>/dev/null; do
sleep 10
ELAPSED=$((ELAPSED + 10))
if [ "$ELAPSED" -ge 900 ]; then # 15-minute cap
TIMED_OUT=1
kill "$PID" 2>/dev/null || true
break
fi
done
if wait "$PID"; then
status=0
else
status=$?
fi
# Append after wait so the command cannot overwrite the timeout record.
if [ "$TIMED_OUT" -eq 1 ]; then
echo "TIMEOUT after 15min" >> "results/${cell_id}.md"
fi
if [ "$TIMED_OUT" -eq 0 ] && [ "$status" -eq 0 ]; then
touch "state/${cell_id}.done"
else
echo "Failed: $cell_id (exit $status, timed_out=$TIMED_OUT); retry on the next run" >&2
fi
15 minutes is my current sweet spot. Normal review tasks finish in 2-5 minutes. Beyond 15 usually means it's stuck in some loop. The timeout is appended to the existing output, and the cell gets no .done marker. The batch moves on; the next run can retry it. This example sends TERM to a single PID; it does not handle child processes or a process that ignores TERM.
Multi-Model Comparison: The Real Data
The original comparison record
The original post records the same review task run against 9 models, with these 7 summaries in its table. It does not record the test date, complete prompt, every model version, or each run's output. Treat the numbers as that usage record, not a ranking of today's models:
| Model | Output Size | My Take |
|---|---|---|
| Claude Opus 4.6 | 10.2 KB | Covers everything. Good when you're new to the codebase. |
| Claude Opus 4.8 | 7.9 KB | Practical: tells you what to fix, not just what's wrong. |
| Claude Opus 4.7 | 5.9 KB | Conclusions only. For quick confirmation. |
| GPT 5.5 | 7.4 KB | Sometimes approaches from a completely different angle. Surprises you. |
| Gemini 3.1 Pro | 7.1 KB | Clean structure, precise scoping. |
| Gemini 3.5 Flash | 4.4 KB | Fastest. Use as a quick filter. |
| GPT 5.4 | 0.3 KB | Refused on that run; the cause was not separately verified. |
Practical takeaways:
Choose for the task. My rotation then was:
- Quick screening: Gemini 3.5 Flash (fastest response on that run)
- Full analysis: Claude Opus 4.6 (when you can't afford to miss anything)
- Second opinion: GPT 5.5 (genuinely different reasoning paths)
Verify after cross-comparing. Agreement can help prioritize a finding, but two models can share the same misunderstanding. A finding from only one model is not grounds for dismissal either. Code, reproduction conditions, and tests settle it.
October 8, 2026 update: use GitLab identifiers
--model and GITLAB_DUO_MODEL take a gitlab_identifier, not a provider API model name. The examples below follow the official model registry, checked on October 8, 2026. Availability depends on your instance and group settings; /model lists the choices in interactive mode.
Scripting multi-model runs is straightforward:
MODELS=("claude_opus_4_6_20260205" "claude_opus_4_7" "gemini_3_5_flash_vertex")
for model in "${MODELS[@]}"; do
glab duo cli run -C "$TARGET" \
--goal "$GOAL" \
--model "$model" \
> "results/${CELL_ID}-${model}.md" 2>&1
sleep 30
done
Narrow the Review, Then Verify Findings
Git Log Anchoring: Giving AI a Map
This technique made the biggest difference in my output quality.
The problem: when you tell AI to "review this repo," it doesn't know where to start. With thousands of files, it has to decide what to look at and what to skip. The result is often: time spent on irrelevant areas, while the actual issues go unexamined.
The fix: give it a map first.
# Pull commits related to a specific topic
git log --all \
--grep='error\|exception\|retry\|timeout\|fallback' \
--regexp-ignore-case \
--name-only \
--format='%h %cs %s' \
> error-handling-anchors.txt
This file tells the AI: "These are the recent changes related to error handling. Focus here."
glab duo cli run -C ~/project \
--goal "Here are recent commits related to error handling:
$(head -50 error-handling-anchors.txt)
Based on these commits:
1. Is the error handling pattern consistent across modules?
2. Are there modules that should have retry logic but don't?
3. Any signs of swallowed exceptions (caught but not handled)?"
Why it works so well: git log --grep surfaces places that were actually changed. Developers spent time fixing things there, which means that's where problems existed. That narrows the investigation. My original “ten times more precise” claim had no controlled comparison behind it and should not be read as a measurement.
Keyword sets I rotate through:
- Error handling:
error|exception|retry|timeout|fallback - Refactoring:
refactor|extract|rename|move|split - Performance:
perf|slow|cache|optimize|batch - Tech debt:
TODO|FIXME|HACK|workaround|temporary
Prompt Validation Gates: 90% Noise Reduction
After using Duo CLI for a while, my biggest pain point wasn't "AI can't find problems." It was "AI finds too many problems, most are false positives."
A code review task returns 20+ findings. You go through them one by one. Turns out 18 are "theoretically an issue but practically can't happen" or "already handled by something else." Time wasted on filtering exceeds time saved by running AI in the first place.
The turning point: adding validation criteria to the end of prompts.
glab duo cli run -C ~/project \
--goal "Review error handling patterns in this module.
Before reporting each finding, verify:
1. Does this issue exist under default configuration? (Not only triggered by special setup)
2. Is there already an existing mechanism handling this? (Check context for catches/retries/guards)
3. Does the impact extend beyond normal usage? (Not something requiring extreme actions to trigger)
4. Is this a real functional issue, not a style preference?
Mark failures as 'Not applicable — reason: ___'.
Only report findings that pass all four checks in detail."
Result from that task: the report went from 21 findings to 2, about a 90% reduction in count. That is not a false-positive rate or an accuracy measurement. The 19 removed findings could include misses; the 2 remaining findings still need human verification.
The insight: make the AI judge twice. First pass finds issues. Second pass validates whether they're real. The gap between those two passes is the manual filtering time you saved.
Validation criteria are customizable per use case. Code review criteria differ from tech debt assessment criteria, but the structure is the same: give the AI a set of standards for "this finding is worth reporting."
The Bigger Toolchain
Last piece: how this fits into a broader workflow. I don't use Duo CLI alone. It's one stage in a pipeline.
| Stage | Tool | Why |
|---|---|---|
| Breadth scan | Duo CLI | Multi-model, batch-capable; my allocation then suited initial screening |
| Deep analysis | Claude Code | Has memory, tool integrations, good for multi-turn |
| Mechanical verification | Codex CLI | Sandboxed execution, runs confirmation scripts |
The flow:
- Duo CLI batch-scans all modules (breadth), 2-3 models cross-compared
- Findings marked "needs deeper look" go to Claude Code for focused analysis (depth)
- Consolidate conclusions, confirm fix direction
Duo CLI's role here is "first filter." It doesn't need to be perfect. It needs to be fast and wide. The next stage still needs a defined scope and verification; it cannot promise to recover every miss.
October 8, 2026 update: compare calls and allocations
The June workflow used the contributor allocation I had then. An Enterprise seat does not automatically provide the same allocation. The billing guide separates Agent Platform usage billing from the seat-based Duo Pro and Enterprise features, and community subscriptions do not receive included credits. Models have different credit multipliers; one task can make multiple LLM calls, and running three models adds cost.
Before a batch, check the namespace Credits balance, usage caps, and on-demand settings. To compare tools, record actual usage for the same task and the findings that survive verification. Being billed in Credits does not make the cost negligible.
Scattered Tips
- Save results as .md, not .txt. AI responses are usually markdown-formatted. Saved as .md, they render beautifully in any viewer.
- Set
GITLAB_DUO_MODELas a default. Saves typing--modelevery time. - Split large repos into modules. Throwing an entire monorepo at one command rarely works well. Target subdirectories or use
--ai-context-itemsto inject only relevant files. - Be specific in your prompts. "Review this code" loses to "review this code's error handling, especially timeout edge cases" every time.
- Keep a prompt template library. Good prompts deserve reuse. I have a folder of templates for different task types. Different job, different template.
Wrapping Up
Advanced Duo CLI usage comes down to three things: batch it, multi-model it, de-noise it. Get those three right and you go from "asking one question at a time" to "scanning an entire codebase at once."
Nothing magical here, just the composability that CLI tools are supposed to have. GitLab put AI in the terminal, and shell scripting superpowers naturally follow.
Hope these patterns are useful. If you've found better approaches, share them in the community. I'm still adjusting my own workflow.