Skip to content

Commit 8902a93

Browse files
committed
feat(cli): allow overriding default coder prompt in exp mcp configure claude-code
1 parent ca56ef5 commit 8902a93

File tree

2 files changed

+70
-13
lines changed

2 files changed

+70
-13
lines changed

cli/exp_mcp.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ func (*RootCmd) mcpConfigureClaudeCode() *serpent.Command {
114114
claudeConfigPath string
115115
claudeMDPath string
116116
systemPrompt string
117+
coderPrompt string
117118
appStatusSlug string
118119
testBinaryName string
119120

@@ -185,8 +186,18 @@ func (*RootCmd) mcpConfigureClaudeCode() *serpent.Command {
185186
reportTaskPrompt = defaultReportTaskPrompt
186187
}
187188

189+
// If a user overrides the coder prompt, we don't want to append
190+
// the report task prompt, as it then becomes the responsibility
191+
// of the user.
192+
actualCoderPrompt := defaultCoderPrompt
193+
if coderPrompt != "" {
194+
actualCoderPrompt = coderPrompt
195+
} else if reportTaskPrompt != "" {
196+
actualCoderPrompt += "\n\n" + reportTaskPrompt
197+
}
198+
188199
// We also write the system prompt to the CLAUDE.md file.
189-
if err := injectClaudeMD(fs, systemPrompt, reportTaskPrompt, claudeMDPath); err != nil {
200+
if err := injectClaudeMD(fs, actualCoderPrompt, systemPrompt, claudeMDPath); err != nil {
190201
return xerrors.Errorf("failed to modify CLAUDE.md: %w", err)
191202
}
192203
cliui.Infof(inv.Stderr, "Wrote CLAUDE.md to %s", claudeMDPath)
@@ -231,6 +242,14 @@ func (*RootCmd) mcpConfigureClaudeCode() *serpent.Command {
231242
Value: serpent.StringOf(&systemPrompt),
232243
Default: "Send a task status update to notify the user that you are ready for input, and then wait for user input.",
233244
},
245+
{
246+
Name: "coder-prompt",
247+
Description: "The coder prompt to use for the Claude Code server.",
248+
Env: "CODER_MCP_CLAUDE_CODER_PROMPT",
249+
Flag: "claude-coder-prompt",
250+
Value: serpent.StringOf(&coderPrompt),
251+
Default: "", // Empty default means we'll use defaultCoderPrompt from the variable
252+
},
234253
{
235254
Name: "app-status-slug",
236255
Description: "The app status slug to use when running the Coder MCP server.",
@@ -603,7 +622,7 @@ Task summaries MUST:
603622
systemPromptEndGuard = "</system-prompt>"
604623
)
605624

606-
func injectClaudeMD(fs afero.Fs, systemPrompt, reportTaskPrompt, claudeMDPath string) error {
625+
func injectClaudeMD(fs afero.Fs, coderPrompt, systemPrompt, claudeMDPath string) error {
607626
_, err := fs.Stat(claudeMDPath)
608627
if err != nil {
609628
if !os.IsNotExist(err) {
@@ -614,7 +633,7 @@ func injectClaudeMD(fs afero.Fs, systemPrompt, reportTaskPrompt, claudeMDPath st
614633
return xerrors.Errorf("failed to create claude config directory: %w", err)
615634
}
616635

617-
return afero.WriteFile(fs, claudeMDPath, []byte(promptsBlock(defaultCoderPrompt, reportTaskPrompt, systemPrompt, "")), 0o600)
636+
return afero.WriteFile(fs, claudeMDPath, []byte(promptsBlock(coderPrompt, systemPrompt, "")), 0o600)
618637
}
619638

620639
bs, err := afero.ReadFile(fs, claudeMDPath)
@@ -647,7 +666,7 @@ func injectClaudeMD(fs afero.Fs, systemPrompt, reportTaskPrompt, claudeMDPath st
647666
cleanContent = strings.TrimSpace(cleanContent)
648667

649668
// Create the new content with coder and system prompt prepended
650-
newContent := promptsBlock(defaultCoderPrompt, reportTaskPrompt, systemPrompt, cleanContent)
669+
newContent := promptsBlock(coderPrompt, systemPrompt, cleanContent)
651670

652671
// Write the updated content back to the file
653672
err = afero.WriteFile(fs, claudeMDPath, []byte(newContent), 0o600)
@@ -658,19 +677,11 @@ func injectClaudeMD(fs afero.Fs, systemPrompt, reportTaskPrompt, claudeMDPath st
658677
return nil
659678
}
660679

661-
func promptsBlock(coderPrompt, reportTaskPrompt, systemPrompt, existingContent string) string {
680+
func promptsBlock(coderPrompt, systemPrompt, existingContent string) string {
662681
var newContent strings.Builder
663682
_, _ = newContent.WriteString(coderPromptStartGuard)
664683
_, _ = newContent.WriteRune('\n')
665684
_, _ = newContent.WriteString(coderPrompt)
666-
667-
// Only include the report task prompt if it's provided
668-
if reportTaskPrompt != "" {
669-
_, _ = newContent.WriteRune('\n')
670-
_, _ = newContent.WriteRune('\n')
671-
_, _ = newContent.WriteString(reportTaskPrompt)
672-
}
673-
674685
_, _ = newContent.WriteRune('\n')
675686
_, _ = newContent.WriteString(coderPromptEndGuard)
676687
_, _ = newContent.WriteRune('\n')

cli/exp_mcp_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,52 @@ test-system-prompt
192192
}
193193
})
194194

195+
t.Run("CustomCoderPrompt", func(t *testing.T) {
196+
t.Setenv("CODER_AGENT_TOKEN", "test-agent-token")
197+
ctx := testutil.Context(t, testutil.WaitShort)
198+
cancelCtx, cancel := context.WithCancel(ctx)
199+
t.Cleanup(cancel)
200+
201+
client := coderdtest.New(t, nil)
202+
_ = coderdtest.CreateFirstUser(t, client)
203+
204+
tmpDir := t.TempDir()
205+
claudeConfigPath := filepath.Join(tmpDir, "claude.json")
206+
claudeMDPath := filepath.Join(tmpDir, "CLAUDE.md")
207+
208+
customCoderPrompt := "This is a custom coder prompt from flag."
209+
210+
// This should include the custom coderPrompt and reportTaskPrompt
211+
expectedClaudeMD := `<coder-prompt>
212+
This is a custom coder prompt from flag.
213+
</coder-prompt>
214+
<system-prompt>
215+
test-system-prompt
216+
</system-prompt>
217+
`
218+
219+
inv, root := clitest.New(t, "exp", "mcp", "configure", "claude-code", "/path/to/project",
220+
"--claude-api-key=test-api-key",
221+
"--claude-config-path="+claudeConfigPath,
222+
"--claude-md-path="+claudeMDPath,
223+
"--claude-system-prompt=test-system-prompt",
224+
"--claude-app-status-slug=some-app-name",
225+
"--claude-test-binary-name=pathtothecoderbinary",
226+
"--claude-coder-prompt="+customCoderPrompt,
227+
)
228+
clitest.SetupConfig(t, client, root)
229+
230+
err := inv.WithContext(cancelCtx).Run()
231+
require.NoError(t, err, "failed to configure claude code")
232+
233+
require.FileExists(t, claudeMDPath, "claude md file should exist")
234+
claudeMD, err := os.ReadFile(claudeMDPath)
235+
require.NoError(t, err, "failed to read claude md path")
236+
if diff := cmp.Diff(expectedClaudeMD, string(claudeMD)); diff != "" {
237+
t.Fatalf("claude md file content mismatch (-want +got):\n%s", diff)
238+
}
239+
})
240+
195241
t.Run("NoReportTaskWhenNoAppSlug", func(t *testing.T) {
196242
t.Setenv("CODER_AGENT_TOKEN", "test-agent-token")
197243
ctx := testutil.Context(t, testutil.WaitShort)

0 commit comments

Comments
 (0)