From bde95d8f67dcc538d71636a4be487bfce5a02209 Mon Sep 17 00:00:00 2001 From: Karthik Rajanna Date: Thu, 13 Aug 2026 15:27:55 -0700 Subject: [PATCH 1/4] fix: scope and forward stepId for the planning-branch lbv hitl The AWAITING_HUMAN_INPUT path already scopes the local-build-verification HITL to the loaded beamed repo and forwards its plan-step id, but the PLANNING branch did neither: it picked the first LBV HITL in the list and returned it without StepInformation. On a multi-repo beam where the job sits in PLANNING at build time, that surfaces a sibling's (or untagged) HITL, so the IDE scope guard cannot confirm ownership and defers the build indefinitely. Bring the PLANNING branch to parity with AWAITING_HUMAN_INPUT: - prefer the in-scope LBV via selectScopedLbvHitl (no-op when scope is empty, so non-beam behavior is unchanged) - forward StepInformation.StepId when present Verified live on a 3-repo beam: each loaded repo now builds and siblings are correctly deferred. --- .../netTransform/atxTransformHandler.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts b/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts index 70649ff0fc..0f30b281df 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts @@ -2001,10 +2001,18 @@ export class ATXTransformHandler { // Plan not available yet } - // Check for pending HITL tasks (e.g. missing packages) - job stays in PLANNING while HITL is pending + // Check for pending HITL tasks (e.g. missing packages) - job stays in PLANNING while HITL is pending. + // Beam multi-repo: several repos can have a pending LBV HITL at once; prefer the loaded repo's + // in-scope one (parity with AWAITING_HUMAN_INPUT). No-op when scope is empty, so non-beam is unchanged. const hitls = await this.listHitls(request.WorkspaceId, request.TransformationJobId) if (hitls && hitls.length > 0) { + const planScopeStepIds = (request.beamScopeStepIds || '') + .split(',') + .map(s => s.trim()) + .filter(s => s.length > 0) + const { scopedLbv: scopedPlanLbv } = this.selectScopedLbvHitl(hitls, planScopeStepIds) const hitl = + scopedPlanLbv || hitls.find(h => h.tag === 'local-build-verification') || hitls.find(h => h.tag === 'missing-packages' || h.tag === 'handle_missing_packages_hitl') || hitls[0] @@ -2036,8 +2044,11 @@ export class ATXTransformHandler { } if (hitl.tag === 'local-build-verification') { this.jobsPastLocalBuild.add(request.TransformationJobId) + // Forward the HITL's plan-step id so a multi-repo beam IDE can confirm this LBV + // belongs to the loaded repo (parity with AWAITING_HUMAN_INPUT). Undefined when absent. + const planLbvStepId = this.getStepId(hitl) this.logging.log( - `ATX: ${jobStatus} job has pending LBV HITL — taskId=${hitl.taskId}; surfacing AWAITING_HUMAN_INPUT to IDE` + `ATX: ${jobStatus} job has pending LBV HITL — taskId=${hitl.taskId} stepId=${planLbvStepId ?? ''}; surfacing AWAITING_HUMAN_INPUT to IDE` ) return { TransformationJob: { @@ -2047,6 +2058,7 @@ export class ATXTransformHandler { } as AtxTransformationJob, HitlTag: hitl.tag, HitlTaskId: hitl.taskId, + StepInformation: planLbvStepId ? { StepId: planLbvStepId } : undefined, TransformationPlan: plan, } as AtxGetTransformInfoResponse } From 3430737bb0d41a61b33c8c0982d509ec3c72b725 Mon Sep 17 00:00:00 2001 From: Karthik Rajanna Date: Fri, 14 Aug 2026 00:28:28 -0700 Subject: [PATCH 2/4] fix: suppress out-of-scope sibling LBVs in the planning branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the PLANNING branch of getTransformInfo to full parity with the EXECUTING / getHitlAgentArtifact paths for multi-repo beam. It already preferred the loaded repo's in-scope LBV and forwarded its stepId, but it did not suppress out-of-scope sibling LBVs: when scope was set with no in-scope LBV it could still surface a sibling's LBV HITL to the IDE. Mirror the sibling branches: - when scope is set and every pending HITL is a sibling's LBV, return plan-only (surface nothing) - otherwise drop out-of-scope LBVs from the fallback pool so a sibling's LBV can't be picked in a mixed pending set Not a live false-green (the IDE scope guard already rejects a HITL whose stepId is out of the loaded subtree) — this restores the LSP-side layer so all three branches behave identically. Non-beam is byte-identical: the new logic is gated on a non-empty beam scope. --- .../netTransform/atxTransformHandler.ts | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts b/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts index 0f30b281df..d50d7be12d 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts @@ -2010,12 +2010,37 @@ export class ATXTransformHandler { .split(',') .map(s => s.trim()) .filter(s => s.length > 0) - const { scopedLbv: scopedPlanLbv } = this.selectScopedLbvHitl(hitls, planScopeStepIds) + const { scopedLbv: scopedPlanLbv, allOutOfScopeLbv: planAllOutOfScopeLbv } = + this.selectScopedLbvHitl(hitls, planScopeStepIds) + // Beam multi-repo parity with EXECUTING / getHitlAgentArtifact: if scope is set and every + // pending HITL is a sibling repo's LBV (none in the loaded repo's subtree), do NOT surface a + // sibling's LBV — return plan-only. Prevents handing the IDE another repo's build HITL. + if (planScopeStepIds.length > 0 && planAllOutOfScopeLbv) { + this.logging.log( + 'ATX: PLANNING job — all pending HITLs are sibling-repo LBV (none in loaded scope); not surfacing' + ) + return { + TransformationJob: { + WorkspaceId: request.WorkspaceId, + JobId: request.TransformationJobId, + Status: jobStatus, + } as AtxTransformationJob, + TransformationPlan: plan, + } as AtxGetTransformInfoResponse + } + // When scope is set but no in-scope LBV matched, drop out-of-scope LBVs from the fallback + // pool so the plain find() below can't select a sibling's LBV (mixed pending-set case). + const planFallbackPool = + planScopeStepIds.length > 0 && !scopedPlanLbv + ? hitls.filter(h => h.tag !== 'local-build-verification') + : hitls const hitl = scopedPlanLbv || - hitls.find(h => h.tag === 'local-build-verification') || - hitls.find(h => h.tag === 'missing-packages' || h.tag === 'handle_missing_packages_hitl') || - hitls[0] + planFallbackPool.find(h => h.tag === 'local-build-verification') || + planFallbackPool.find( + h => h.tag === 'missing-packages' || h.tag === 'handle_missing_packages_hitl' + ) || + planFallbackPool[0] this.logging.log(`ATX: Found HITL task - tag: ${hitl.tag}, hasArtifact: ${!!hitl.agentArtifact}`) // For missing packages HITL, try to download artifact if available if (hitl.tag === 'handle_missing_packages_hitl' || hitl.tag === 'missing-packages') { From 584e2e070168b865e239c2f10a824c1094560807 Mon Sep 17 00:00:00 2001 From: Karthik Rajanna Date: Mon, 17 Aug 2026 04:45:32 -0700 Subject: [PATCH 3/4] fix: harden beam LSP stepId coalescing and tidy beam docs - getStepId uses || so an empty-string id falls through to the next spelling (was ?? which let an empty id defeat coalescing and scope matching) - move the normalizeBeamRepo JSDoc onto normalizeBeamRepo (was stranded above getStepId) --- .../netTransform/atxTransformHandler.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts b/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts index d50d7be12d..6566297acf 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/netTransform/atxTransformHandler.ts @@ -841,11 +841,6 @@ export class ATXTransformHandler { } } - /** - * Normalize a beam-map repo item into the PascalCase shape the C# IDE consumes, - * tolerant of key-name variants from the web writer (repoName/repo/name, - * artifactId/beamArtifactId, stepId/planStepId, targetFramework/tfm). - */ /** * The wire contract with the web orchestrator / FES is not yet pinned to a single * spelling: a plan-step / HITL id arrives as stepId, planStepId, or parentStepId @@ -855,7 +850,9 @@ export class ATXTransformHandler { */ private getStepId(obj: any): string | undefined { if (obj == null || typeof obj !== 'object') return undefined - return obj.stepId ?? obj.planStepId ?? obj.parentStepId ?? undefined + // Use || (not ??) so an empty-string id falls through to the next spelling; an empty + // stepId must not short-circuit coalescing and then fail every scope match. + return obj.stepId || obj.planStepId || obj.parentStepId || undefined } /** @@ -879,6 +876,11 @@ export class ATXTransformHandler { return { scopedLbv, allOutOfScopeLbv } } + /** + * Normalize a beam-map repo item into the PascalCase shape the C# IDE consumes, + * tolerant of key-name variants from the web writer (repoName/repo/name, + * artifactId/beamArtifactId, stepId/planStepId, targetFramework/tfm). + */ private normalizeBeamRepo(r: BeamMapRepo | null | undefined): Omit { const o: BeamMapRepo = r || {} return { From 4663ecbbd218b0e6c9618dfc5b1b342ededfbbd9 Mon Sep 17 00:00:00 2001 From: Karthik Rajanna Date: Mon, 17 Aug 2026 23:30:06 -0700 Subject: [PATCH 4/4] test: cover beam LBV planning-scope and getStepId coalescing - PLANNING branch: a sibling repo's out-of-scope LBV is not surfaced - getStepId: empty stepId falls through to planStepId/parentStepId (|| not ??) --- .../tests/atxTransformHandler.test.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/server/aws-lsp-codewhisperer/src/language-server/netTransform/tests/atxTransformHandler.test.ts b/server/aws-lsp-codewhisperer/src/language-server/netTransform/tests/atxTransformHandler.test.ts index 7f9b4fddbd..b8a0894b5c 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/netTransform/tests/atxTransformHandler.test.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/netTransform/tests/atxTransformHandler.test.ts @@ -497,6 +497,30 @@ describe('ATXTransformHandler - getTransformInfo', () => { expect((handler as any).jobsPastLocalBuild.has('job-123')).to.be.true }) + it('should NOT surface a sibling repo LBV when status is PLANNING and all pending LBVs are out of the loaded scope', async () => { + // Beam multi-repo: the IDE loaded one repo (beamScopeStepIds = its subtree). Every pending + // HITL is a local-build-verification for a DIFFERENT (sibling) repo's plan step. Surfacing + // one would make the IDE build the loaded solution against a sibling's HITL → false-green. + // Mirrors the EXECUTING/getHitlAgentArtifact guard: return the plan-only view instead. + getJobStub.resolves({ statusDetails: { status: 'PLANNING' } }) + getTransformationPlanStub.resolves({ Root: { Children: [] } }) + listHitlsStub.resolves([ + { tag: 'local-build-verification', taskId: 'task-sib1', stepId: 'sibling-step-1' }, + { tag: 'local-build-verification', taskId: 'task-sib2', stepId: 'sibling-step-2' }, + ]) + + const result = await handler.getTransformInfo({ + ...baseRequest, + beamScopeStepIds: 'loaded-step-a,loaded-step-b', + }) + + // Plan-only view: original job status preserved, no HITL surfaced to the IDE. + expect(result?.TransformationJob.Status).to.equal('PLANNING') + expect(result?.HitlTag).to.be.undefined + expect(result?.HitlTaskId).to.be.undefined + expect(result?.TransformationPlan).to.deep.equal({ Root: { Children: [] } }) + }) + it('should filter pre-job mode-selection -checkpoint HITL before LBV has run', async () => { getJobStub.resolves({ statusDetails: { status: 'PLANNING' } }) getTransformationPlanStub.resolves({ Root: { Children: [] } }) @@ -4167,6 +4191,15 @@ describe('ATXTransformHandler - Beam to IDE', () => { expect(g('str')).to.be.undefined expect(g({ other: 'x' })).to.be.undefined }) + + it('skips an empty-string stepId and falls through to planStepId / parentStepId (|| not ??)', () => { + const g = (o: any) => (handler as any).getStepId(o) + // Empty stepId must NOT short-circuit coalescing (the ??→|| hardening) — an empty + // string is falsy under ||, so the non-empty planStepId is returned. + expect(g({ stepId: '', planStepId: 'step-1' })).to.equal('step-1') + // Empty stepId + empty planStepId → fall all the way through to parentStepId. + expect(g({ stepId: '', planStepId: '', parentStepId: 'parent-1' })).to.equal('parent-1') + }) }) // --- normalizeBeamRepo: tolerant wire-shape mapping ---