From 89af58b6dc9a7ff869d26ca61ffbd59bd7ed0510 Mon Sep 17 00:00:00 2001
From: Dotta <34892728+cryppadotta@users.noreply.github.com>
Date: Thu, 16 Jul 2026 15:53:03 -0500
Subject: [PATCH] Clarify task-level model overrides in issue properties
(#9710)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Thinking Path
> - Paperclip is the control plane where operators configure and inspect
AI-agent work.
> - An issue can override the assigned agent's primary model for that
task.
> - The issue-properties UI labeled that per-task setting as `Custom ·
`, which could be read as a property of the model rather than a
replacement of the agent default.
> - Operators need the UI to distinguish the agent's primary model from
an issue-specific override at both the collapsed summary and selection
point.
> - This pull request renames the lane presentation to `Override` and
adds concise provenance text without changing the stored lane value or
adapter configuration behavior.
> - The benefit is that operators can immediately understand which model
will run and why it differs from the agent default.
## Linked Issues or Issue Description
No matching public GitHub issue was found. Related implementation work:
#9700 moved Codex ACPX model configuration to startup and is already on
`master`; #9355 also addresses ACP session-config rejection behavior.
Those PRs concern execution behavior, while this PR is limited to
clarifying the issue-level override UI.
Bug report details:
- **What happened?** The issue-properties Model row displayed `Custom ·
` for a task-level
`assigneeAdapterOverrides.adapterConfig.model`. Operators could misread
`Custom` as describing the model itself and could not see that the value
replaced the agent's primary model for this issue.
- **Expected behavior:** The UI should explicitly identify a task-level
model as an override and explain that it replaces the agent's primary
model for the issue.
- **Steps to reproduce:** Configure an agent with a primary model, set a
different model on an issue, and inspect the Model row and model picker
in Issue Properties.
- **Paperclip version or commit:** Reproduced on the pre-change branch
derived from current `master`.
- **Deployment mode:** Local development or any deployment using the
board UI.
- **Installation method:** Built from source.
- **Agent adapters involved:** Adapter-agnostic; any adapter exposing
model selection.
- **Database mode:** Not database-related.
- **Access context:** Board operator viewing issue properties.
- **Relevant logs or output:** Not applicable; this is a presentation
ambiguity.
- **Relevant config:** An issue-level
`assigneeAdapterOverrides.adapterConfig.model` differing from the
assigned agent's primary model.
- **Additional context:** The internal lane identifier remains `custom`;
only user-facing copy and explanatory text change.
- **Privacy checklist:** No private instance links, internal ticket IDs,
secrets, usernames, or local paths are included.
## What Changed
- Renamed the collapsed issue model label from `Custom · ` to
`Override · ` and added a provenance tooltip.
- Renamed the model-picker lane from `Custom` to `Override` and added
explanatory subtext at the selection point.
- Updated the Issue Properties component test to assert the new label.
- Added an isolated Storybook fixture for the task model override state
so visual review has a stable target.
## Verification
- `pnpm exec vitest run ui/src/components/IssueProperties.test.tsx` — 43
tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `git diff --check public-gh/master...HEAD` — passed.
- `pnpm check:token-gates` — the changed files are clean, but the
repo-wide command currently reports nine unrelated `#9627` comment
references already present on `master` as color literals.
## Risks
- Low risk: this changes display strings and explanatory copy only;
override storage, lane identifiers, API contracts, and execution
behavior are unchanged.
- The global token-gate false positive may also appear in CI until the
unrelated `#9627` references on `master` are allowlisted or the scanner
ignores issue-number comments.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent assisted this PR preparation using medium
reasoning, repository-aware shell execution, Git/GitHub tooling, and
local test/typecheck execution. The hosted runtime did not expose an
exact model ID or context-window size to this session.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip
---
ui/src/components/IssueProperties.test.tsx | 2 +-
.../issue-properties/IssueProperties.tsx | 15 +++++--
.../stories/issue-management.stories.tsx | 39 +++++++++++++++++++
3 files changed, 52 insertions(+), 4 deletions(-)
diff --git a/ui/src/components/IssueProperties.test.tsx b/ui/src/components/IssueProperties.test.tsx
index 497250de9c..6a346577c2 100644
--- a/ui/src/components/IssueProperties.test.tsx
+++ b/ui/src/components/IssueProperties.test.tsx
@@ -1631,7 +1631,7 @@ describe("IssueProperties", () => {
await flush();
await flush();
- expect(container.textContent).toContain("Custom · gpt-5.4 · high");
+ expect(container.textContent).toContain("Override · gpt-5.4 · high");
expect(container.textContent).toContain("Model lane");
// Wait for the adapter-models query to resolve so the model options render.
diff --git a/ui/src/components/issue-properties/IssueProperties.tsx b/ui/src/components/issue-properties/IssueProperties.tsx
index 911228a24b..49e199b2af 100644
--- a/ui/src/components/issue-properties/IssueProperties.tsx
+++ b/ui/src/components/issue-properties/IssueProperties.tsx
@@ -512,9 +512,13 @@ export function IssueProperties({
assigneeOverrideThinkingEffort,
assigneeOverrideChrome ? "Chrome" : "",
].filter(Boolean);
+ const summary = details.length > 0 ? `Override · ${details.join(" · ")}` : "Override · adapter options";
return (
- 0 ? `Custom · ${details.join(" · ")}` : "Custom adapter options"}>
- Custom{details.length > 0 ? ` · ${details.join(" · ")}` : " adapter options"}
+ 0 ? ` (${details.join(" · ")})` : ""}`}
+ >
+ {summary}
);
}
@@ -537,7 +541,7 @@ export function IssueProperties({
)}
onClick={() => setAssigneeOverrideLane(lane)}
>
- {lane === "primary" ? "Primary" : lane === "cheap" ? "Cheap" : "Custom"}
+ {lane === "primary" ? "Primary" : lane === "cheap" ? "Cheap" : "Override"}
))}
@@ -551,6 +555,11 @@ export function IssueProperties({
: <>· falls back to the primary model if no cheap profile is configured>}
) : null}
+ {assigneeOverrideLane === "custom" ? (
+
+ Task-level model override — replaces the agent's primary model for this issue.
+
+ ) : null}
{assigneeOverrideLane === "custom" ? (
<>
diff --git a/ui/storybook/stories/issue-management.stories.tsx b/ui/storybook/stories/issue-management.stories.tsx
index ed021a4c56..20ba434755 100644
--- a/ui/storybook/stories/issue-management.stories.tsx
+++ b/ui/storybook/stories/issue-management.stories.tsx
@@ -62,6 +62,19 @@ const primaryIssue: Issue = {
documentSummaries: issueDocumentSummaries,
currentExecutionWorkspace: storybookExecutionWorkspaces[0]!,
};
+const modelOverrideIssue: Issue = {
+ ...primaryIssue,
+ id: "issue-model-override-visual",
+ identifier: "MOD-1",
+ issueNumber: 1,
+ title: "Verify task-level model override provenance",
+ assigneeAdapterOverrides: {
+ adapterConfig: {
+ model: "gpt-5.6",
+ modelReasoningEffort: "high",
+ },
+ },
+};
const childIssues = storybookIssues.filter((issue) => issue.parentId === primaryIssue.id);
const longProject: Project = {
...storybookProjects[0]!,
@@ -311,6 +324,27 @@ function IssuePropertiesLongValuePane({ inline = false }: { inline?: boolean })
);
}
+function IssuePropertiesModelOverridePane() {
+ return (
+
+
+
+
Properties
+
+ undefined}
+ onUpdate={() => undefined}
+ inline
+ />
+
+
+
+
+ );
+}
+
function ColumnConfigurationMatrix() {
const [columns, setColumns] = useState(visibleColumns);
const visibleColumnSet = useMemo(() => new Set(columns), [columns]);
@@ -888,6 +922,11 @@ export const IssuePropertiesLongValuesMobile: Story = {
render: () => ,
};
+export const IssuePropertiesModelOverride: Story = {
+ name: "IssueProperties - task model override",
+ render: () => ,
+};
+
function ModelProfileLedgerStandalone() {
return (