Skip to content

Commit 5e41fb5

Browse files
wesmclaude
andauthored
Prevent agents from delegating reviews to roborev skills (#307)
## Summary - Adds an explicit instruction to all review system prompts telling agents to perform the review directly, not delegate to external skills or CLI tools - Fixes Codex loading `SKILL.md` and invoking `roborev review` instead of analyzing the diff itself Closes #306 ## Test plan - [ ] Verify prompt package tests pass - [ ] Run a codex review and confirm it no longer invokes `roborev review` - [ ] Run a claude-code review and confirm no skill invocation 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 67735df commit 5e41fb5

File tree

3 files changed

+53
-9
lines changed

3 files changed

+53
-9
lines changed

internal/prompt/prompt.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import (
1414
// If the prompt with diffs exceeds this, we fall back to just commit info
1515
const MaxPromptSize = 250 * 1024
1616

17+
// noSkillsInstruction tells agents not to delegate the review to external
18+
// tools or skills. Codex and Claude Code discover roborev skills in the
19+
// user's environment and will try to invoke them instead of performing the
20+
// review directly (see #306).
21+
const noSkillsInstruction = `
22+
23+
IMPORTANT: You are being invoked by roborev to perform this review directly. Do NOT use any external skills, slash commands, or CLI tools (such as "roborev review") to delegate this task. Perform the review yourself by analyzing the diff provided below.`
24+
1725
// SystemPromptSingle is the base instruction for single commit reviews
1826
const SystemPromptSingle = `You are a code reviewer. Review the git commit shown below for:
1927

internal/prompt/templates.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ func getSystemPrompt(agentName string, promptType string, now func() time.Time)
3737
tmplName := fmt.Sprintf("templates/%s_%s.tmpl", agentName, templateType)
3838
content, err := templateFS.ReadFile(tmplName)
3939
if err == nil {
40-
return appendDateLine(string(content), now)
40+
base := string(content)
41+
if promptType != "run" {
42+
base += noSkillsInstruction
43+
}
44+
return appendDateLine(base, now)
4145
}
4246

4347
// Fallback to default constants
@@ -61,7 +65,7 @@ func getSystemPrompt(agentName string, promptType string, now func() time.Time)
6165
default:
6266
base = SystemPromptSingle
6367
}
64-
return appendDateLine(base, now)
68+
return appendDateLine(base+noSkillsInstruction, now)
6569
}
6670

6771
// appendDateLine adds the current UTC date to a system prompt.

internal/prompt/templates_test.go

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ func TestGetSystemPrompt(t *testing.T) {
2020
geminiReviewPrompt := getSystemPrompt("gemini", "review", mockNow)
2121

2222
type testCase struct {
23-
name string
24-
agent string
25-
command string
26-
wantContains []string
27-
wantExact string // if set, checks for exact match
28-
wantNotDefault bool // if true, ensures it's not SystemPromptSingle (default)
29-
wantEmpty bool
23+
name string
24+
agent string
25+
command string
26+
wantContains []string
27+
wantNotContains []string
28+
wantExact string // if set, checks for exact match
29+
wantNotDefault bool // if true, ensures it's not SystemPromptSingle (default)
30+
wantEmpty bool
3031
}
3132

3233
tests := []testCase{
@@ -87,6 +88,31 @@ func TestGetSystemPrompt(t *testing.T) {
8788
command: "run",
8889
wantEmpty: true,
8990
},
91+
{
92+
name: "Review includes no-skills instruction",
93+
agent: "claude-code",
94+
command: "review",
95+
wantContains: []string{"Do NOT use any external skills"},
96+
},
97+
{
98+
name: "Security includes no-skills instruction",
99+
agent: "claude-code",
100+
command: "security",
101+
wantContains: []string{"Do NOT use any external skills"},
102+
},
103+
{
104+
name: "Address includes no-skills instruction",
105+
agent: "claude-code",
106+
command: "address",
107+
wantContains: []string{"Do NOT use any external skills"},
108+
},
109+
{
110+
name: "Gemini run excludes no-skills instruction",
111+
agent: "gemini",
112+
command: "run",
113+
wantContains: []string{"Do NOT explain your process"},
114+
wantNotContains: []string{"Do NOT use any external skills"},
115+
},
90116
}
91117

92118
for _, tc := range tests {
@@ -125,6 +151,12 @@ func TestGetSystemPrompt(t *testing.T) {
125151
t.Errorf("got prompt missing %q. Got start: %s", substr, snippet)
126152
}
127153
}
154+
155+
for _, substr := range tc.wantNotContains {
156+
if strings.Contains(got, substr) {
157+
t.Errorf("prompt should NOT contain %q", substr)
158+
}
159+
}
128160
})
129161
}
130162
}

0 commit comments

Comments
 (0)