feat(task-board): configurable task initial prompt per column, with {{vars}} - #6848
feat(task-board): configurable task initial prompt per column, with {{vars}}#6848pedrofrxncx wants to merge 9 commits into
Conversation
…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
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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>
… 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.
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…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.
There was a problem hiding this comment.
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
| {!editsPrompt && prompt && ( | ||
| <p className="whitespace-pre-wrap text-xs text-muted-foreground"> | ||
| {prompt} | ||
| </p> | ||
| )} |
There was a problem hiding this comment.
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>
| {!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; |
There was a problem hiding this comment.
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>
| 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", () => { |
There was a problem hiding this comment.
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.", |
There was a problem hiding this comment.
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>
Summary
A board column's automation
promptused to be a single lead line that the sandbox harness ignored outright. It is now the run's whole opening message, as a template.{{taskTitle}},{{taskDescription}},{{taskId}},{{jiraId}},{{jiraUrl}},{{repoContext}},{{prContext}},{{prBullet}}.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.nullback, so a reset column keeps tracking changes to the shipped default instead of freezing a copy of it.{{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.promptalready exists, andnullalready meant "the default".Affected areas
packages/shared/src/task-initial-prompt.ts(new) — default template, variable registry, renderer. Carries thesuper-agent-sandboxprompt-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 fmtclean.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.
{{instruction}},{{taskTitle}},{{taskDescription}},{{taskId}},{{jiraId}},{{jiraUrl}},{{repoContext}},{{prBullet}}, and{{prContext}}per card.{{...}}is a full template; a plain instruction stays a lead line in the default prompt's{{instruction}}slot.nullback after the write lands so the blur autosave can't overwrite it.{{name}}is left verbatim rather than blanked, and an empty variable takes only its own separating blank line with it.task_board_column_automations.promptalready exists andnullalready meant the default.org_board_columnsflag; Studio's own lanes render their translated labels, and a mirrored column keeps its tracker's name even when its key collides.Written for commit 138256b. Summary will update on new commits.