feat(jira): support structured custom fields in Jira Update tool#5921
feat(jira): support structured custom fields in Jira Update tool#5921mzxchandra wants to merge 10 commits into
Conversation
…in Jira Update
The Jira Update tool only accepted a single customFieldId + string
customFieldValue and wrote it raw, so it could not represent Jira's
option/user/cascading field shapes or set more than one custom field per
call.
- Add a `customFields` param: array of { fieldId, type, value } where type
is text | number | select | multiselect | userpicker | multiuserpicker |
cascading | raw. Serializes each into the Jira REST v3 fields shape.
- Add serializeJiraCustomField + buildJiraCustomFields helpers in
tools/jira/utils.ts (mirrors toAdf; unit-tested directly).
- Merge legacy customFieldId/customFieldValue into the same pipeline as a
raw passthrough; customFields wins on fieldId collision. Backward compatible.
- Custom fields are applied after simple fields under one PUT `fields` body,
so simple + custom fields coexist. ADF description auto-wrap preserved.
- Extend jiraUpdateBodySchema contract and JiraUpdateParams; document the
new shape in the tool param descriptions.
- Add route + utils unit tests asserting exact `fields` payloads.
…-structured-custom-fields
…lizer
Pre-landing review hardening:
- select now respects an explicit { value } / { id } object instead of
re-applying the numeric-id heuristic to it (bare scalars keep the heuristic).
- userpicker / multiuserpicker unwrap a { accountId } object instead of
stringifying it to "[object Object]".
Adds unit tests for both paths.
|
@mzxchandra is attempting to deploy a commit to the Sim Team on Vercel. A member of the Team first needs to authorize it. |
PR SummaryMedium Risk Overview The update route no longer inlines legacy Tool params, types, and broad unit/route tests cover serialization, 400s on bad input, backward compatibility, and combined simple + custom payloads. Reviewed by Cursor Bugbot for commit eed2573. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryExtends Jira issue updates with validated, type-aware structured custom fields.
Confidence Score: 5/5The PR appears safe to merge. No blocking failures remain in the fixes associated with the previous review threads. Important Files Changed
Reviews (7): Last reviewed commit: "fix(jira): also reject cascading child c..." | Re-trigger Greptile |
…cading
Review round (Greptile P1 + Cursor):
- Contract: replace the permissive customFields `value: z.unknown()` with a
discriminated union on `type`, so a shape/value mismatch (select as { label },
userpicker as { email }, non-numeric number) is rejected at the boundary
instead of serializing into a malformed value that would make Jira reject the
whole combined update.
- Serializer: a cascading entry with no resolvable parent now returns undefined
and is skipped, instead of emitting the literal "undefined" as the parent
value. buildJiraCustomFields skips any entry whose serialization is undefined.
- Add tests for both plus boundary rejection of mismatched shapes.
|
@cursor review |
Round 2 (Greptile + Cursor): cascading was validated more loosely than
select/userpicker — value accepted any record and child was z.unknown(), so an
unresolvable shape like { id: '10' } or an object-valued parent/child passed the
boundary and was then either silently dropped or serialized to '[object
Object]', rejecting the whole combined update. Constrain cascading parent/child
to scalars (matching the other option types); an arbitrary record now 400s at
the boundary. Adds a route test for the rejection.
|
@cursor review |
… options
Round 3 (Greptile + Cursor) boundary hardening:
- cascading array values are capped at [parent, child] (.max(2)) so a 3+ element
array is rejected instead of silently truncated by the serializer.
- text values must be strings; a bare number (common from LLM tool calls) is
rejected rather than passed through to a string-typed Jira field.
- select/multiselect option values must be non-empty, so { value: '' } / { id: '' }
/ an empty array element can't serialize into an invalid Jira option.
Adds route tests for all three rejections.
|
@cursor review |
…iases
Round 4 (Greptile): a cascading object like { parent: 'A', value: 'B' } passed
validation but the serializer always uses parent and silently discarded value.
Add a refine rejecting the ambiguous case at the boundary. Adds a route test.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
1 issue from previous review remains unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8743c54. Configure here.
Round 5 (Cursor + Greptile):
- cascading scalars must be non-empty (min 1), matching option/userpicker, so
'', [''], { parent: '' } are rejected at the boundary instead of passing
validation and then being silently dropped by the serializer.
- reject a cascading entry that sets child both at the top level and inside
value (the serializer would keep one and discard the other). Adds route tests.
|
@cursor review |
Completes the round-5 conflicting-child refine, which only checked the object form of value.child. A [parent, child] array plus a top-level child now also rejects at the boundary (nested child is value[1] for the array form). Adds a route test for the array case.
|
@cursor review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 19cd38e. Configure here.
Round 7 (Cursor):
- customFields tool param was type: 'json', which the schema builder exposes to
LLMs as a JSON Schema object while the contract requires an array — a model
emitting an object/map would 400. Switch to type: 'array' with an items schema
describing { fieldId, type, value }, matching the array params in jira/write.ts.
- text custom-field values must be non-empty (min 1), so '' is rejected at the
boundary instead of passing validation and being silently skipped — consistent
with select/userpicker/cascading. Adds a route test.
|
@cursor review |

Summary
Extends the Jira Update tool so it can faithfully write structured Jira custom fields, and lets multiple custom fields + simple fields be sent together in one update.
Previously the tool accepted only a single
customFieldId+ a single stringcustomFieldValue, serialized raw (fields[customfield_X] = "someString"). That can't represent Jira REST v3's option/user/cascading shapes, so select, multi-select, user-picker, and cascading custom fields could not be set at all.New optional
customFieldsparam: an array of{ fieldId, type, value }wheretypeis one oftext | number | select | multiselect | userpicker | multiuserpicker | cascading | raw. Serialization:fields[customfield_X]select{ value }(or{ id }for a numeric option id; explicit{value}/{id}object respected)multiselect[ { value }, ... ]userpicker{ accountId }(unwraps a{ accountId }object)multiuserpicker[ { accountId }, ... ]cascading{ value, child: { value } }(parent/child from explicitchild,{parent,child}, or[parent,child])text/numbernumbercoerces numeric strings)rawThe legacy
customFieldId/customFieldValueparams still work, normalized internally into the same pipeline as arawentry (byte-for-byte identical to prior behavior). If both are supplied,customFieldswins onfieldIdcollision. Custom fields are applied after the simple fields under onePUT, so summary/description/priority/labels/etc. and custom fields coexist. Description ADF auto-wrap is preserved.Scope is limited to the Jira update path. No OAuth/scope changes, no schema/DB changes.
Type of Change
Testing
apps/sim/tools/jira/utils.test.ts(serializer + merge/precedence) andapps/sim/app/api/tools/jira/update/route.test.ts(asserts the exact PUTfieldspayload per type, backward-compat, collision, combined simple+custom, ADF wrap).buildJiraCustomFieldsserializer →PUT /rest/api/3/issue/{key}→ read the issue back. 11/11 shapes accepted and stored correctly, including combined simple+custom in one call and the legacy path.bun run check:api-validationpasses. Typecheck clean for all changed files.Reviewers: focus on
serializeJiraCustomField/buildJiraCustomFieldsinapps/sim/tools/jira/utils.tsand thejiraCustomFieldEntrySchemaaddition inapps/sim/lib/api/contracts/selectors/jira.ts.Known follow-up (not in this PR):
customFieldsis reachable via the tool API and via an LLM/agent tool call, but the Jira block UI doesn't expose it yet (blocks/blocks/jira.tsforwards only the legacy single field). A future change can add acustomFieldssubblock so it's configurable in the visual builder.Checklist