Skip to content

feat(task-board): configurable task initial prompt per column, with {{vars}} - #6848

Open
pedrofrxncx wants to merge 9 commits into
mainfrom
claude/configurable-task-prompt
Open

feat(task-board): configurable task initial prompt per column, with {{vars}}#6848
pedrofrxncx wants to merge 9 commits into
mainfrom
claude/configurable-task-prompt

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

A board column's automation prompt used to be a single lead line that the sandbox harness ignored outright. It is now the run's whole opening message, as a template.

  • Variables substituted per card: {{taskTitle}}, {{taskDescription}}, {{taskId}}, {{jiraId}}, {{jiraUrl}}, {{repoContext}}, {{prContext}}, {{prBullet}}.
  • An unset rule renders DEFAULT_TASK_INITIAL_PROMPT — byte-identical to the prompt shipped today. That is also what the settings textarea shows, so "what does this column actually send?" is answerable without reading the source.
  • Reset to default writes null back, so a reset column keeps tracking changes to the shipped default instead of freezing a copy of it.
  • So a column can be set to just {{taskTitle}} or {{jiraId}}.

An unknown {{name}} is left verbatim (a typo you can see in the run's first message beats one that silently vanished); empty variables collapse their blank lines.

The Decopilot fallback still composes its own message — a template reaching it falls back to the built-in lead rather than being pasted in with literal {{...}}.

No migration: task_board_column_automations.prompt already exists, and null already meant "the default".

Affected areas

  • packages/shared/src/task-initial-prompt.ts (new) — default template, variable registry, renderer. Carries the super-agent-sandbox prompt-region markers, so the deployment-admin prompt editor follows it here (registry path updated).
  • apps/api/src/tools/task-board/claude-code-task-run.ts — the builder now produces variables + renders; branch selection (repo block, re-run lead) unchanged and still unit-tested.
  • apps/web/src/views/settings/board-columns.tsx — per-column template editor, variable list, Reset to default.

Testing

  • bun test packages/shared/src/task-initial-prompt.test.ts apps/api/src/tools/task-board/ — new renderer tests + new builder tests (template replaces the message, Jira key exposed, unset renders the default). Pre-existing failures in that folder are the DB-backed ones (no local Postgres).
  • bun run check, bun run lint, bun run fmt clean.

Screenshots

Not captured — settings UI change; say the word and I'll run the app and grab one.


Summary by cubic

A column's automation prompt is now the run's whole opening message, rendered as a template per card — previously it was a single lead line the sandbox harness ignored. The prompt editor appears on the queue lane, where a card waits unassigned and its rule actually delegates it, and on Studio's own board too, which previously had no way to reach it.

  • Substitutes {{instruction}}, {{taskTitle}}, {{taskDescription}}, {{taskId}}, {{jiraId}}, {{jiraUrl}}, {{repoContext}}, {{prBullet}}, and {{prContext}} per card.
  • A rule carrying {{...}} is a full template; a plain instruction stays a lead line in the default prompt's {{instruction}} slot.
  • An unset rule renders the shipped default, byte-identical to today's prompt; the settings textarea shows it, and Reset to default writes null back after the write lands so the blur autosave can't overwrite it.
  • Unknown {{name}} is left verbatim rather than blanked, and an empty variable takes only its own separating blank line with it.
  • The Decopilot fallback still composes its own message — a template reaching it falls back to the built-in lead.
  • A rule on any other column still shows its prompt and remove button, so it can't run unseen.
  • No migration needed: task_board_column_automations.prompt already exists and null already meant the default.
  • Only the "move cards here when" picker stays behind the org_board_columns flag; Studio's own lanes render their translated labels, and a mirrored column keeps its tracker's name even when its key collides.
  • The board columns section now sits below the Jira section and starts collapsed, so its queries only run once opened.

Written for commit 138256b. Summary will update on new commits.

Review in cubic

Pedro França added 2 commits September 1, 2026 21:19
…pening message, with {{vars}}

A column rule's `prompt` used to be a single lead line the sandbox harness
ignored outright. It is now the full opening message of the run, as a template:
`{{taskTitle}}`, `{{taskDescription}}`, `{{jiraId}}`, `{{repoContext}}`,
`{{prContext}}` and friends are substituted per card, so a board can shrink the
task prompt to `{{taskTitle}}` or reorder it without a deploy.

An unset rule renders `DEFAULT_TASK_INITIAL_PROMPT` — byte-identical to the
prompt shipped today — and that is what the settings textarea shows and what
"Reset to default" writes back (null), so a reset column keeps tracking changes
to the shipped default instead of freezing a copy of it.

The Decopilot fallback still composes its own message; a template reaching it is
ignored in favour of its built-in lead rather than pasted in with literal
`{{...}}`.

No migration: `task_board_column_automations.prompt` already exists.
…ask-prompt

# Conflicts:
#	apps/api/src/tools/task-board/claude-code-task-run.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/src/tools/task-board/claude-code-task-run.ts">

<violation number="1" location="apps/api/src/tools/task-board/claude-code-task-run.ts:235">
P2: When a card description contains intentional blank lines, rendering the default prompt rewrites that card content because the renderer collapses newlines globally. Preserve substituted variable contents while collapsing only the blank lines introduced by empty template variables.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

): TaskInitialPromptVars {
return {
taskTitle: task.title,
taskDescription: descriptionBlock(task.description),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a card description contains intentional blank lines, rendering the default prompt rewrites that card content because the renderer collapses newlines globally. Preserve substituted variable contents while collapsing only the blank lines introduced by empty template variables.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/tools/task-board/claude-code-task-run.ts, line 235:

<comment>When a card description contains intentional blank lines, rendering the default prompt rewrites that card content because the renderer collapses newlines globally. Preserve substituted variable contents while collapsing only the blank lines introduced by empty template variables.</comment>

<file context>
@@ -191,140 +197,134 @@ export async function resolveTaskRepoChoice(
+): TaskInitialPromptVars {
+  return {
+    taskTitle: task.title,
+    taskDescription: descriptionBlock(task.description),
+    taskId: task.id,
+    jiraId: jiraKeyFromUrl(task.externalUrl ?? null),
</file context>

Comment thread packages/shared/src/task-initial-prompt.ts Outdated
Comment thread packages/shared/src/task-initial-prompt.ts Outdated
Comment thread apps/web/src/views/settings/board-columns.tsx Outdated
Comment thread apps/web/src/views/settings/board-columns.tsx Outdated
Comment thread apps/web/src/views/settings/board-columns.tsx Outdated
… too

The section was gated on `org_board_columns`, so an org that hadn't mirrored a
tracker's columns had no way to reach the prompt at all — the one setting that
is not about mirroring. Every board now lists its columns; only the "move cards
here when" picker stays behind the flag, since Studio's own lanes already mean
what they are named. Canonical lanes render their translated label rather than
the raw key.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/web/src/views/settings/board-columns.tsx Outdated
Pedro França added 6 commits September 1, 2026 21:33
…n only

Every column with a rule got the full template editor, which promoted a short
instruction like "review the diff" on some other lane into the run's entire
message. Now only the lane where agent work starts (role `in_progress`) edits
the template; every other column keeps the short instruction box it had, and
that instruction lands in the default prompt's new `{{instruction}}` slot
instead of replacing it.
The agent-run rule is now addable on the lane where work starts and nowhere
else. A rule that already exists on another column still renders — with its
instruction and its remove button — because the engine fires whatever rule it
finds on the column a card lands in, so hiding one would leave it running with
no way to switch it off.
…pse it

It is the longest block on the page and the least often read, so it now sits
last and starts collapsed — which also means its two queries only run once
someone opens it.
- the renderer no longer collapses blank lines globally, which rewrote a card's
  own description; an empty variable now takes only its own separating blank
  line with it
- unknown-variable lookup uses Object.hasOwn, so {{constructor}}/{{toString}}
  stay verbatim as documented instead of rendering an inherited value
- dropped TASK_INITIAL_PROMPT_MAX_LENGTH: nothing enforced it and the automation
  tool's own cap is the real one
- Reset applies to the textarea only after the write lands, and no longer loses
  to the blur autosave it was discarding
- variable descriptions moved to the i18n dictionaries, so pt-BR is not English
- a mirrored column keeps its tracker's name even when its key collides with one
  of Studio's canonical lanes
… rule

Restricting it to In Progress made it unreachable in the normal flow: a card is
delegated on whichever column it lands in UNASSIGNED — the queue lane, usually —
and `runColumnAutomation` bails once the card is owned, so by the time Studio
moves it to In Progress the rule there can never fire. A local card proved it:
the In Progress template was saved and the run still went out on the default,
dispatched by the todo column's rule.
A card waits UNASSIGNED in the queue lane, so that column's rule is the one that
delegates it; `runColumnAutomation` bails on an owned card, so every later lane
is reached already owned by the run its own rule started. The rule and its
prompt are therefore offered on the lane with role `todo` (Studio's Todo, or a
mirrored board's column mapped to it) and nowhere else.

A rule already sitting on another column still shows its prompt and its remove
button — the engine fires whatever rule it finds, so hiding one would leave it
running with no way to switch it off.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 existing issue remains and 4 new issues found across 8 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/src/views/settings/board-columns.tsx">

<violation number="1" location="apps/web/src/views/settings/board-columns.tsx:268">
P2: When an existing non-queue rule has `prompt: null`, this condition renders no prompt at all, even though null means the shipped default. Render the effective default so retained rules remain understandable.</violation>
</file>

<file name="apps/api/src/tools/task-board/claude-code-task-run.ts">

<violation number="1" location="apps/api/src/tools/task-board/claude-code-task-run.ts:227">
P2: When a saved instruction contains an unmatched or literal `{{`, this check treats it as a full template and drops the default task context. Detect a complete `{{...}}` placeholder instead, or retain the default prompt for malformed braces.</violation>
</file>

<file name="apps/api/src/tools/task-board/claude-code-task-run.test.ts">

<violation number="1" location="apps/api/src/tools/task-board/claude-code-task-run.test.ts:284">
P3: This test doesn't exercise repo choices, so it doesn't belong in the "repo choices" describe block. Move it into the main buildClaudeCodeTaskPrompt describe (or a dedicated one) so the block stays a truthful grouping for tests that actually pass repoChoices.</violation>
</file>

<file name="apps/web/src/i18n/pt-br/settings.ts">

<violation number="1" location="apps/web/src/i18n/pt-br/settings.ts:81">
P3: In the new pt-BR variable descriptions, 'vazio' does not agree with its feminine subject. 'A descrição do card ... vazio' and 'A chave da issue do Jira ... ou vazio' should use 'vazia' to agree with 'descrição' and 'chave'. Change both to 'vazia' for grammatical consistency with the rest of the translation.</violation>
</file>

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment on lines +268 to +272
{!editsPrompt && prompt && (
<p className="whitespace-pre-wrap text-xs text-muted-foreground">
{prompt}
</p>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When an existing non-queue rule has prompt: null, this condition renders no prompt at all, even though null means the shipped default. Render the effective default so retained rules remain understandable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/views/settings/board-columns.tsx, line 268:

<comment>When an existing non-queue rule has `prompt: null`, this condition renders no prompt at all, even though null means the shipped default. Render the effective default so retained rules remain understandable.</comment>

<file context>
@@ -219,57 +265,80 @@ function BoardColumnCard({
-              <li
-                key={name}
-                className="flex gap-2 text-xs text-muted-foreground"
+          {!editsPrompt && prompt && (
+            <p className="whitespace-pre-wrap text-xs text-muted-foreground">
+              {prompt}
</file context>
Suggested change
{!editsPrompt && prompt && (
<p className="whitespace-pre-wrap text-xs text-muted-foreground">
{prompt}
</p>
)}
{!editsPrompt && (
<p className="whitespace-pre-wrap text-xs text-muted-foreground">
{prompt ?? DEFAULT_TASK_INITIAL_PROMPT}
</p>
)}

*/
function taskPromptTemplate(instruction: string | undefined): string {
const trimmed = instruction?.trim();
return trimmed?.includes("{{") ? trimmed : DEFAULT_TASK_INITIAL_PROMPT;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a saved instruction contains an unmatched or literal {{, this check treats it as a full template and drops the default task context. Detect a complete {{...}} placeholder instead, or retain the default prompt for malformed braces.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/tools/task-board/claude-code-task-run.ts, line 227:

<comment>When a saved instruction contains an unmatched or literal `{{`, this check treats it as a full template and drops the default task context. Detect a complete `{{...}}` placeholder instead, or retain the default prompt for malformed braces.</comment>

<file context>
@@ -206,15 +207,26 @@ export function buildClaudeCodeTaskPrompt(
+ */
+function taskPromptTemplate(instruction: string | undefined): string {
+  const trimmed = instruction?.trim();
+  return trimmed?.includes("{{") ? trimmed : DEFAULT_TASK_INITIAL_PROMPT;
+}
+
</file context>
Suggested change
return trimmed?.includes("{{") ? trimmed : DEFAULT_TASK_INITIAL_PROMPT;
return trimmed?.match(/\{\{\s*\w+\s*\}\}/) ? trimmed : DEFAULT_TASK_INITIAL_PROMPT;

});

// A rule on any column but In Progress is still a lead line, not a template.
test("a plain instruction leads the default prompt instead of replacing it", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This test doesn't exercise repo choices, so it doesn't belong in the "repo choices" describe block. Move it into the main buildClaudeCodeTaskPrompt describe (or a dedicated one) so the block stays a truthful grouping for tests that actually pass repoChoices.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/tools/task-board/claude-code-task-run.test.ts, line 284:

<comment>This test doesn't exercise repo choices, so it doesn't belong in the "repo choices" describe block. Move it into the main buildClaudeCodeTaskPrompt describe (or a dedicated one) so the block stays a truthful grouping for tests that actually pass repoChoices.</comment>

<file context>
@@ -280,6 +280,16 @@ describe("buildClaudeCodeTaskPrompt repo choices", () => {
   });
 
+  // A rule on any column but In Progress is still a lead line, not a template.
+  test("a plain instruction leads the default prompt instead of replacing it", () => {
+    const prompt = buildClaudeCodeTaskPrompt(task, repo, {
+      instruction: "Review the diff and leave comments.",
</file context>

"O que a regra da coluna manda fazer, ou a frase de abertura do pr\u00f3prio agente.",
"settings.boardColumns.var.taskTitle": "O t\u00edtulo do card.",
"settings.boardColumns.var.taskDescription":
"A descri\u00e7\u00e3o do card, j\u00e1 rotulada \u2014 vazio quando n\u00e3o tem nenhuma.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: In the new pt-BR variable descriptions, 'vazio' does not agree with its feminine subject. 'A descrição do card ... vazio' and 'A chave da issue do Jira ... ou vazio' should use 'vazia' to agree with 'descrição' and 'chave'. Change both to 'vazia' for grammatical consistency with the rest of the translation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/i18n/pt-br/settings.ts, line 81:

<comment>In the new pt-BR variable descriptions, 'vazio' does not agree with its feminine subject. 'A descrição do card ... vazio' and 'A chave da issue do Jira ... ou vazio' should use 'vazia' to agree with 'descrição' and 'chave'. Change both to 'vazia' for grammatical consistency with the rest of the translation.</comment>

<file context>
@@ -72,8 +72,24 @@ export const settings = {
+    "O que a regra da coluna manda fazer, ou a frase de abertura do pr\u00f3prio agente.",
+  "settings.boardColumns.var.taskTitle": "O t\u00edtulo do card.",
+  "settings.boardColumns.var.taskDescription":
+    "A descri\u00e7\u00e3o do card, j\u00e1 rotulada \u2014 vazio quando n\u00e3o tem nenhuma.",
+  "settings.boardColumns.var.taskId":
+    "O id do card, de que as ferramentas do board usadas no run precisam.",
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant