Skip to content

Commit 07fb4b0

Browse files
cdervclaude
andcommitted
Address review findings batch 3 for posit-connect-cloud provider
Three hardening fixes: - Trim POSIT_CONNECT_CLOUD_ACCOUNT_ID env var to prevent CI/CD copy-paste whitespace from causing silent match failure (.17) - Enforce minimum 5-second device code poll interval per RFC 8628 §3.5 to prevent tight loop if server returns interval=0 (.18) - Catch transient errors (HTTP 500, network timeouts) during 30-minute revision polling with consecutive error threshold before aborting, since ~1,800 poll calls makes transient failures likely (.19) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2fff202 commit 07fb4b0

2 files changed

Lines changed: 26 additions & 4 deletions

File tree

src/publish/posit-connect-cloud/api/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ export async function pollForToken(
375375
initialInterval: number,
376376
expiresIn: number,
377377
): Promise<TokenResponse> {
378-
let interval = initialInterval;
378+
let interval = Math.max(initialInterval, 5);
379379
const params = new URLSearchParams({
380380
scope: kOAuthScope,
381381
client_id: env.clientId,

src/publish/posit-connect-cloud/posit-connect-cloud.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {
4141
writeStoredToken,
4242
writeStoredTokens,
4343
} from "./api/index.ts";
44-
import { Account, PositConnectCloudToken } from "./api/types.ts";
44+
import { Account, PositConnectCloudToken, Revision } from "./api/types.ts";
4545

4646
export const kPositConnectCloud = "posit-connect-cloud";
4747
const kPositConnectCloudDescription = "Posit Connect Cloud";
@@ -356,7 +356,7 @@ async function publish(
356356
"No publishable accounts found for the provided access token.",
357357
);
358358
}
359-
const envAccountId = Deno.env.get(kPositConnectCloudAccountIdVar);
359+
const envAccountId = Deno.env.get(kPositConnectCloudAccountIdVar)?.trim();
360360
if (envAccountId) {
361361
const match = publishable.find((a) => a.id === envAccountId);
362362
if (!match) {
@@ -501,6 +501,8 @@ async function publish(
501501
// Poll revision status
502502
const pollStartTime = Date.now();
503503
let lastStatus = "";
504+
let consecutiveErrors = 0;
505+
const kMaxConsecutiveErrors = 5;
504506
while (true) {
505507
if (Date.now() - pollStartTime > kRevisionPollTimeoutMs) {
506508
throw new Error(
@@ -509,7 +511,27 @@ async function publish(
509511
);
510512
}
511513

512-
const revision = await client.getRevision(revisionId);
514+
let revision: Revision;
515+
try {
516+
revision = await client.getRevision(revisionId);
517+
consecutiveErrors = 0;
518+
} catch (err) {
519+
if (err instanceof ApiError || err instanceof TypeError) {
520+
consecutiveErrors++;
521+
publishDebug(
522+
`Revision poll error (${consecutiveErrors}/${kMaxConsecutiveErrors}): ${err}`,
523+
);
524+
if (consecutiveErrors >= kMaxConsecutiveErrors) {
525+
throw new Error(
526+
"Lost connection to Connect Cloud while monitoring deployment. " +
527+
"The deployment may still succeed — check the dashboard for status.",
528+
);
529+
}
530+
await sleep(2000);
531+
continue;
532+
}
533+
throw err;
534+
}
513535

514536
// Log status changes
515537
if (revision.status && revision.status !== lastStatus) {

0 commit comments

Comments
 (0)