chore(jira): non-blocking review nits deferred from #895 — preview claim-latch recovery, Amplify receiver observability, ADF/param typing
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 32/100
- Issue type
- Refactor
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- aws, github, typescript
- Domain
- backend-api-design, databases, observability, testing-qa
Research direction
Start by splitting the review items into the proposed groups, then read the named entry points in cdk/src/handlers/shared/jira-deployment-preview.ts, jira-preview.ts, jira-feedback.ts, github-deployment-status.ts, and iteration-heartbeat-sweep.ts. Check the related types and tests before choosing one group; done means the selected state, observability, typing, or test gaps are addressed without redoing fixes already present in 1f2e37ab.
Written by the indexing model from the issue text.
Description
Component
CDK Jira adapter and screenshot pipeline — cdk/src/handlers/shared/jira-deployment-preview.ts, jira-preview.ts, jira-feedback.ts, github-deployment-status.ts, cdk/src/handlers/iteration-heartbeat-sweep.ts, cdk/src/handlers/shared/types.ts
Describe the feature
Non-blocking findings deferred from the review of #895 (Jira deployment-preview feedback). The four review blockers were fixed in 1f2e37ab — bounded delivery with deadline cancellation, safe convergence outcomes, mutation-sensitive race coverage, and corrected routing docs — and the items below were deliberately left out of that round. All line numbers are as of 1f2e37ab; the symbol names are the anchors.
The first item is the one with a customer-visible wrong outcome; the rest are drift-prevention, observability, and type ergonomics.
- 1. The preview claim latch conflates definite failure with uncertain failure, and never releases.
jira-deployment-preview.ts:124-160.jira_preview_claimedis a one-way latch — nothing incdk/srcremoves it. On a Jira 429/503 the response was received, so nothing was created, yet the claim stays set and the preview is permanently lost for that task: every later delivery takes theConditionalCheckFailedExceptionbranch, finds no comment id, logs onewarn, and returns.writeCommentalready distinguishes "Jira answered non-2xx" (jira-feedback.ts:284-286) from itscatch(genuinely uncertain), andJiraPostResultcollapses both into{ ok: false, retryable }. The documented trade — "Jira may have committed before a transport timeout" — is correct for the uncertain half only. Add a third outcome state and release the claim (REMOVE jira_preview_claimed, conditional onattribute_not_exists(jira_preview_comment_id)) on definite failure. - 2. POST succeeds but the comment-id write fails → the issue shows a stale screenshot forever.
jira-deployment-preview.ts:150-158. State becomes claimed ✓ / comment exists in Jira ✓ / id unrecorded ✗, so every subsequent deploy hits the!commentIdbranch at:141-146and gives up. The Jira issue keeps the first commit's screenshot indefinitely — worse than showing nothing, because a stale screenshot is indistinguishable from a current one. Write the claim and the comment id in one conditional update where possible; failing that, this branch and item 1's should log aterrorwith anerror_idfromconstants/errorIds.tsrather thanwarn, since "a comment exists in Jira that we can never update again" is an operator-actionable inconsistency. - 3. An empty-string comment id is persisted as if valid.
jira-feedback.ts:718returns{ ok: true, commentId: '' }(pre-existing, deliberately, so a caller does not create a duplicate).jira-deployment-preview.ts:157writes that verbatim, and the duplicate branch at:141accepts it becausetypeof '' === 'string'.updateIssueCommentAdfthen fails its/^\d+$/guard forever, logging "Refusing to update Jira comment with an invalid id" — which misdescribes the cause: the id is not corrupt, it was never captured. Only persist a non-empty id, and route the empty case to item 2's "posted but unaddressable" path. - 4. The heartbeat sweep drops the Jira result on the floor.
iteration-heartbeat-sweep.ts:152-161—if (result.ok) edited += 1; continue;with noelse. A Jira tenant whose token has been revoked produces a sweep reportingedited: 0, indistinguishable from a sweep with nothing eligible, and with no per-task line correlating the failure (unlike the non-Jira path, whose failures land in thecatchwithtask_id). Mirror thatcatch, and count attempts separately from successes. - 5.
jira_preview_claimed?: booleancontradicts its own DynamoDB predicate.types.ts:335. The condition isattribute_not_exists(jira_preview_claimed), so the invariant is presence, not truth — but the type admitsfalse, which a future writer would read as "not claimed" while writing it permanently blocks delivery. One-character fix:readonly jira_preview_claimed?: true;. Better still, collapse it withjira_preview_comment_idinto one field so{claimed: false, comment_id: 'x'}stops being representable. - 6. The ADF document is the one structured outbound payload in the repo that is not typed. It is
Record<string, unknown> | nullat every boundary —buildAdfDocument,postIssueCommentAdf,updateIssueCommentAdf,TaskRecord.jira_iteration_status.body— while the repo already types inbound ADF (jira-adf.ts:41AdfNode) and the closest outbound analogue in full (slack-blocks.ts:27-88SlackBlock/SlackMessage). The cost is visible atjira-preview.ts:71, which needs two casts and a?? []to concatenate two documents; with anAdfDocumentinterface that line is cast-free, and a non-arraycontent(from an older or partially-writtenjira_iteration_status) stops being representable — today it spreads characters into the posted body. - 7. Positional
stringruns in the two new public functions.deliverJiraDeploymentPreviewnow takes 9 parameters andupdateJiraIterationComment8.(screenshotUrl, previewUrl)swapped compiles, passes the allowlist, and is caught by no test — the labels simply point at the wrong URLs.(tableName, registryTableName)swapped cross-wires the task table and the registry and fails silently (ValidationException, swallowed).updateJiraIterationCommentgained six call sites in #895, so this gets more expensive with each one. An options object, or three one-line brands (TaskTableName,TaskId,JiraCommentId), makes every swap a compile error. - 8.
normalizeAmplifyPreviewCheckreturns a barenullfor ~19 distinct rejection reasons, with no log anywhere.github-deployment-status.ts:67-104; the receiver returns{ skipped_check: true }atgithub-webhook.ts:106. Compare the neighbours: a dedup hit logsinfowith the key, an unrelated event type logsinfowith the type, a malformeddeployment_statuslogswarnand returns 400. So a dropped Amplify preview is strictly less observable than a duplicate one. If AWS renames the check, changes the app-slug shape, or moves previews to a custom domain, the entire Amplify path dies permanently and silently behind a green webhook delivery page. Return a discriminated result carrying the reason, log it, and thenosemgrep: ts-silent-success-maskingsuppression at:88should become removable. - 9. Existing Amplify operators are silently broken by the environment filter. The normalizer hardcodes
environment: 'Preview'(github-deployment-status.ts:101) while the receiver still appliesSCREENSHOT_TARGET_ENVIRONMENT(github-webhook.ts:130). An operator following the previous guide has that set to a branch name; they subscribe to Check runs as the new guide instructs and every Amplify check drops with onlyskipped_environmentin the HTTP response body — while setting it toPreviewbreaks their existing branch-deploy path, because the filter is a single fixed string, so the two Amplify modes are mutually exclusive. Either exempt thecheck_runpath (check name +aws-amplify-consoleapp owner +pr-N.<app-id>.amplifyapp.comhostname already prove it is a PR preview) or set the synthetic environment totargetEnv; either way add a migration note for operators currently on a branch-name value. - 10. The validated PR number is discarded, then re-derived from the SHA.
github-deployment-status.ts:90-103provespr-<n>matches a listed pull request whosehead.shaequalscheck.head_sha, then dropspreview[1];github-webhook-processor.ts:195asks GitHub again by SHA. When one SHA heads two open PRs (retargeted base, stacked series, duplicated PR), the screenshot can be posted onto a different PR — andpersistScreenshotUrlthen resolves the task from that PR'sheadRefName, so the whole Jira/Linear delivery chain follows the wrong task. Green, plausible, wrong. Carry the validated number on the normalized payload and prefer it; if the SHA lookup disagrees, logerrorand skip rather than guess. - 11. Module naming.
jira-preview.ts's dominant export isupdateJiraIterationComment, imported by four handlers — it is the iteration-comment writer, not a preview module. Sitting besidejira-deployment-preview.ts(the delivery orchestrator) the two are near-indistinguishable at an import site;jira-iteration-comment.tswould read truer. Its module-privaterender()is also very generic for a shared module. - 12. Two small consequences of the
1f2e37abfix itself. (a)jira-deployment-preview.ts:48computesdocumentand throws at the top of thetry, so a disallowed screenshot URL now aborts the iteration path too — where the document is never used, sinceupdateJiraIterationCommentre-renders from durable state. A misconfiguredSCREENSHOT_PUBLIC_HOSTwould skip the entire status-comment update, including completion metrics and the PR link, rather than just omitting the preview block; moving the check into the standalone branch keeps it fail-closed without that side effect. (b):104tightenedtask.head_sha && task.head_sha !== shatotask.head_sha !== sha, which correctly closes a hole but means iteration records written beforehead_shawas persisted now receive no preview at all — worth confirming that is intended and, if so, noting it. - 13. Residual test gaps. Jira 4xx vs 5xx classification is not pinned at the
jira-previewlayer (both collapse into onewarn);screenshot.jira_missing_registryis unexercised;Number.isSafeInteger(check.id)has no non-safe-integer case; and the400 Invalid webhook payloadbranch is unreachable in tests because the'null'/'[]'bodies are sent ascheck_runand returnskipped_checkfirst — adeployment_statusevent with bodynullwould exercise it.
Use case
Items 1-3 are the same failure class #698 set out to eliminate: a preview that cannot be delivered is accepted silently and resurfaces as either nothing at all or a confidently-wrong artifact on a customer's Jira issue. Items 8-10 are the Amplify receiver's observability and correctness edges — the path has no signal when it stops working. The rest keep the invariants #895 established enforceable rather than remembered.
Proposed solution
Items 4, 5, 11, 12a and 13 are small and can land as one cleanup PR. Items 1-3 belong together — they are one state machine (claim / posted / unaddressable) and are best fixed as a unit with the outcome type widened. Items 6 and 7 are mechanical but touch many call sites; worth doing before updateJiraIterationComment gains a ninth parameter. Items 8-10 concern the Amplify check_run receiver, which is tracked separately by #900 — they are recorded here to keep the review's findings in one place, and could equally be folded into that issue.
Other information
- Raised during review of #895, which is otherwise ready — the four review blockers were fixed in
1f2e37ab. - Already fixed in
1f2e37ab, do not redo: deadline cancellation and bounded lookups (JiraPreviewBudget,MAX_CANDIDATE_READS,POST_CAPTURE_RESERVE_MS8s→30s,screenshot.jira_budget_exhausted/screenshot.jira_lookup_limited); convergence returningok: trueafter successful PUTs plusjira.preview.task_missingfor an absent record;LookupResultonpersistScreenshotUrlso a lookup failure skips both channel deliveries;jiraPreviewDocumentreturningnulland the standalone path rejecting it before claiming; the page-limitthrowbecoming abreak; mutation-sensitive race coverage (resolution-order assertions, PUT call counts,terminal: trueat the fan-out and reconciler call sites, a literalConditionExpressionassertion); thePutSecretValuejustification comment and the enumeratedAwsSolutions-IAM5reason; the routing sentence inJIRA_SETUP_GUIDE.mdplus the full emitted-event list; and the stalepreservePreview,parseMarkdownRunssubset,TASK_TABLE-unset, andALL_OLDcomments. - Related: #900 (Amplify PR preview check runs), #863 (the best-effort
LookupResultconvention these paths should follow), #697 (maturing Jira iteration status comments).
- Dominant language
- TypeScript
- Stars
- 146
- Forks
- 46
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 26
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from aws-samples/sample-autonomous-cloud-coding-agents
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug v1
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
bug v1
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
-
documentation P2 security
Difficulty 2/5 1-2 days Newbie friendliness 74/100
-
documentation
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
aws-samples/sample-autonomous-cloud-coding-agents#767 · 2 comments ·
All issues in aws-samples/sample-autonomous-cloud-coding-agents
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
receptron/mulmoterminal#2264 ·
-
documentation
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
components-web-app/docs#96 ·
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
simonsobs/tileviewer#114 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100