fix: resolve orphan-sweep null-assignee filter regression (#8018)

> Resubmits #5925 by @digitalflanker-ux (rebased onto current `master`;
original commit authorship preserved).

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The issues list API powers orphan-sweep and board inbox views that
filter by assignee
> - `assigneeAgentId=null` is a valid query-string sentinel for
"unassigned issues"
> - A regression caused that sentinel to throw 500 instead of filtering
correctly
> - This pull request restores null-sentinel parsing in the route and
service layers
> - The benefit is reliable orphan-sweep and unassigned-issue queries
without server errors

## Linked Issues or Issue Description

Refs #5891 (paired fix — land together)

**Bug:** `GET /api/companies/:id/issues?assigneeAgentId=null` returned
HTTP 500. Expected: HTTP 200 with only unassigned issues. Malformed
UUIDs should return 4xx, not 500.

## What Changed

- Parse `assigneeAgentId=null` in the issues list route and pass a JS
`null` filter to the service
- Handle malformed assignee IDs with HTTP 422 in the route layer
- Extend `issueService.list` to treat `assigneeAgentId: null` as `IS
NULL` SQL filter
- Add route-level and service-level regression tests

## Verification

- `pnpm exec vitest run
server/src/__tests__/issue-list-assignee-filter-routes.test.ts
server/src/__tests__/issues-service.test.ts`
- result: 2 files passed, 79 tests passed

## Risks

Low risk — scoped to query-parameter parsing and list filtering; no
schema or API contract changes beyond fixing the regression.

## Model Used

None — human-authored original fix by @digitalflanker-ux; rebased and
test-harness adjustments by Paperclip cluster cleanup.

## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

## Cross-references and status (maintainer)

- Pairs with #5891 — both fix the `assigneeAgentId=null` issues-list
regression and should land together.
- Supersedes #5925 (fork branch could not be force-pushed; this is the
operator-mergeable resubmission).

---------

Co-authored-by: openclaw <digitalflanker@gmail.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin Foley
2026-06-12 09:20:00 -07:00
committed by GitHub
parent d9ea1bf9e1
commit 3fbab2e6db
5 changed files with 611 additions and 6 deletions
+21 -1
View File
@@ -52,6 +52,7 @@ import {
updateIssueSchema,
getClosedIsolatedExecutionWorkspaceMessage,
isClosedIsolatedExecutionWorkspace,
isUuidLike,
normalizeIssueIdentifier as normalizeIssueReferenceIdentifier,
type CompanySearchQuery,
type CompanySearchResponse,
@@ -2447,6 +2448,8 @@ export function issueRoutes(
const sortField = req.query.sortField as string | undefined;
const sortDir = req.query.sortDir as string | undefined;
const hasPlanDocument = parseOptionalBooleanQuery(req.query.hasPlanDocument);
const assigneeAgentFilterRaw = req.query.assigneeAgentId;
let assigneeAgentId: string | null | undefined;
if (assigneeUserFilterRaw === "me" && (!assigneeUserId || req.actor.type !== "board")) {
res.status(403).json({ error: "assigneeUserId=me requires board authentication" });
@@ -2488,12 +2491,29 @@ export function issueRoutes(
res.status(400).json({ error: "hasPlanDocument must be true or false when provided" });
return;
}
if (assigneeAgentFilterRaw !== undefined) {
if (typeof assigneeAgentFilterRaw !== "string") {
res.status(422).json({ error: "assigneeAgentId must be a UUID or 'null'" });
return;
}
const normalizedAssigneeAgentFilter = assigneeAgentFilterRaw.trim();
if (normalizedAssigneeAgentFilter.length === 0) {
assigneeAgentId = undefined;
} else if (normalizedAssigneeAgentFilter.toLowerCase() === "null") {
assigneeAgentId = null;
} else if (isUuidLike(normalizedAssigneeAgentFilter)) {
assigneeAgentId = normalizedAssigneeAgentFilter;
} else {
res.status(422).json({ error: "assigneeAgentId must be a UUID or 'null'" });
return;
}
}
const offset = parsedOffset ?? 0;
const rawResult = await svc.list(companyId, {
attention: attention === "blocked" ? "blocked" : undefined,
status: req.query.status as string | string[] | undefined,
assigneeAgentId: req.query.assigneeAgentId as string | undefined,
assigneeAgentId,
participantAgentId: req.query.participantAgentId as string | undefined,
assigneeUserId,
touchedByUserId,