diff --git a/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.test.tsx b/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.test.tsx
index e6792dc26..9e72c6eef 100644
--- a/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.test.tsx
+++ b/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.test.tsx
@@ -125,6 +125,39 @@ describe('FindingContextSection', () => {
});
});
+ it('saves against the page run id, not the provider per-issue runId', async () => {
+ // Maced's Issue.runId is a per-issue run id that has no ownership-marker
+ // row, so sending it makes the API's upsert → getReport → assertRunOwnership
+ // 404 (the bare "HTTP 404:" toast). The PUT must carry the pentest run id
+ // the page is built on.
+ permissionsMock.canUpdatePentest = true;
+ contextsMock.saveContext.mockResolvedValue(undefined);
+ const user = userEvent.setup();
+
+ render(
+ ,
+ );
+
+ await user.type(
+ screen.getByRole('textbox'),
+ 'Accepted by design — reads are non-sensitive.',
+ );
+ await user.click(screen.getByRole('button', { name: /save context/i }));
+
+ await waitFor(() => {
+ expect(contextsMock.saveContext).toHaveBeenCalledWith({
+ issueId: 'issue_1',
+ runId: 'run_page',
+ context: 'Accepted by design — reads are non-sensitive.',
+ });
+ });
+ });
+
it('lets pentest:update users remove a saved note', async () => {
permissionsMock.canUpdatePentest = true;
contextsMock.contextByIssueId = new Map([['issue_1', savedNote]]);
diff --git a/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.tsx b/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.tsx
index 225657776..b7d206de7 100644
--- a/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.tsx
+++ b/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.tsx
@@ -44,7 +44,13 @@ export function FindingContextSection({
const { contextByIssueId, isSaving, saveContext, removeContext } =
usePentestFindingContexts(orgId, targetUrl);
const existing = contextByIssueId.get(issue.id);
- const resolvedRunId = issue.runId ?? runId ?? null;
+ // Save against the pentest run id the page is built on (run.id), NOT
+ // issue.runId. Maced's Issue.runId is a per-issue run id with no
+ // ownership-marker row, so the API's upsert → getReport →
+ // assertRunOwnership would 404 (surfaced as a bare "HTTP 404:" toast).
+ // The page run id is the one we fetched the report/issues with and that
+ // the securityPenetrationTestRun marker is keyed on.
+ const resolvedRunId = runId ?? null;
const form = useForm({
resolver: zodResolver(findingContextSchema),