Skip to content

[Tenant] Document resources not cascade-deleted on account deletion#30836

Open
ngayerie wants to merge 3 commits into
cloudflare:productionfrom
ngayerie:ngayerie/SPM-3366
Open

[Tenant] Document resources not cascade-deleted on account deletion#30836
ngayerie wants to merge 3 commits into
cloudflare:productionfrom
ngayerie:ngayerie/SPM-3366

Conversation

@ngayerie

Copy link
Copy Markdown
Collaborator

Summary

Documents that certain resources are NOT automatically deleted when deleting an account via the Tenant API, and provides the correct deletion sequence.

Problem

The current documentation states: "Account deletion is permanent and will delete any zones or other resources under the account."

This is misleading. In practice, the following resources survive account deletion:

  • Logpush jobs - continue delivering logs
  • Zero Trust Gateway configurations - may continue resolving DNS queries

This has caused 34+ customer escalations over 6+ years (tracked in RM-25090). A single customer (AFS/Accenture Federal Services) had 23 child accounts in this broken state simultaneously.

Solution

Added a caution block to the "Delete account" section in /tenant/how-to/manage-accounts/ that:

  1. Lists resources that are NOT cascade-deleted
  2. Provides the 3-step workaround sequence:
    • DELETE /accounts/{id}/gateway
    • DELETE /accounts/{id}/access/organizations
    • DELETE /accounts/{id}

Tickets

  • SPM-3366
  • Related: RM-25090, CUSTESC-59435

Adds caution block to Delete account section explaining:
- Logpush jobs continue delivering logs after account deletion
- Zero Trust Gateway configs may continue resolving DNS queries
- 3-step workaround: delete Gateway, delete Access org, then delete account

This addresses a documentation gap that has caused 34+ customer escalations
over 6+ years (tracked in RM-25090).

Addresses SPM-3366
@github-actions

Copy link
Copy Markdown
Contributor

Hey there, we've marked this pull request as stale because there's no recent activity on it. This label helps us identify PRs that might need updates (or to be closed out by our team if no longer relevant).

@github-actions github-actions Bot added the stale label May 30, 2026
@ngayerie

ngayerie commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator Author

Hi @rianvdm
Is that something you could help review/merge?
Thanks!

@rianvdm rianvdm assigned rianvdm and unassigned rianvdm Jun 2, 2026
@rianvdm

rianvdm commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Looks like I can't approve this one. @kodster28 ?

@github-actions github-actions Bot removed the stale label Jun 3, 2026

@colbywhite colbywhite left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ngayerie , please update the code owner for this file.

Addresses feedback on PR
@jhutchings1 jhutchings1 requested review from a team and kodster28 as code owners June 24, 2026 15:03
@cloudflare-docs-bot

cloudflare-docs-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review

🚨 3 critical, ⚠️ 29 warnings, 💡 12 suggestions found in commit de1a141.

Code Review

This code review is in beta and may not always be helpful — use your judgment.

Critical (3)
File Issue
.flue/lib/github.ts line 340 Incorrect GitHub API endpoint — Line 340 calls https://api.github.com/repos/${org}/teams/${team}/memberships/${username}, but GitHub's team membership API lives under /orgs/, not /repos/. This causes every team-based CODEOWNERS check to 404 and return false, silently denying access to legitimate team code owners. Fix: Change the URL to https://api.github.com/orgs/${org}/teams/${team}/memberships/${username}.
.github/workflows/auto-format-apply.yml line 11 Missing workflow permission to read artifacts — The Download patch artifact step calls github.rest.actions.listWorkflowRunArtifacts and downloadArtifact using the workflow's default GITHUB_TOKEN, but the permissions block only grants contents: read and pull-requests: read. It lacks actions: read, so the artifact download will fail with a 403 error and the patch can never be applied. Fix: Add actions: read to the top-level permissions block, or pass github-token: ${{ steps.app-token.outputs.token }} to the actions/github-script step that downloads the artifact so it uses a token with artifact read access.
.flue/lib/code-review-render.ts line 23 Severity schema mismatch — The finding schema only allows warning and suggestion, but the code-review skill explicitly emits a third severity level critical. Any model output containing a critical finding will fail schema validation and likely break the review render pipeline. Fix: Add "critical" to the severity picklist in both the active and ignored_by_reviewer finding schemas.
Warnings (28)
File Issue
public/turnstile/spin/index.md line 40 Wizard script path mismatch — The wizard instructs running scripts/auth-probe.sh (and scripts/widget-create.sh, scripts/worker-deploy.sh, etc.), but the bootstrap section writes the helper scripts to /tmp/turnstile-spin-scripts/ and exports an unused TURNSTILE_SPIN_SCRIPTS variable. A literal execution of the wizard commands will fail because the scripts/ directory is not populated by the documented bootstrap flow. Fix: Make the wizard commands use the configured script directory (e.g. $TURNSTILE_SPIN_SCRIPTS/auth-probe.sh) or add an explicit step to copy the bootstrapped scripts into scripts/ before the wizard runs.
public/turnstile/spin/index.md line 632 Trailing newline added to Worker secretworker-deploy.sh passes the secret with echo "$WIDGET_SECRET" | npx wrangler secret put TURNSTILE_SECRET_KEY .... The default echo appends a newline, so the value stored in the Worker ends in \n. If the runtime compares the secret literally against Cloudflare's Turnstile secret, siteverify will fail for the freshly deployed Worker. Fix: Pass the secret without a trailing newline: printf '%s' "$WIDGET_SECRET" | npx wrangler secret put TURNSTILE_SECRET_KEY .... Also update the retry instruction on line 59 to use printf '%s' instead of echo.
public/turnstile/spin/index.md line 476 Widget API response not validated before parsingvalidate.sh curls /accounts/$ACCOUNT_ID/challenges/widgets/$SITEKEY and immediately feeds the response into jq -r '.result.domains[]'. If the API returns a 403, 404, or any error JSON without .result.domains, the python fallback throws a KeyError and the subsequent check reports domains as missing instead of surfacing the underlying API failure. Fix: Check the HTTP status and the API success field before extracting domains; emit a structured error response (e.g. check:widget_lookup) when the widget API call itself fails.
.flue/workflows/code-review-orchestrator.ts line 288 Partial Promise.all failure leaves stale pending comment — The Promise.all that stages the diff and posts the 'review in progress' comment is not wrapped in error handling. If writeDiffToWorkspace rejects, the pending comment has already been posted but the failure-comment replacement path is never reached, leaving users with a stuck 'in progress' message. Fix: Wrap the staging + placeholder post in a try/catch and render/post the failure comment when staging fails.
.flue/workflows/code-review-orchestrator.ts line 553 Final GitHub post/reaction errors are unhandled — postOrUpdateComment, removeReactionFromComment, and addReactionToComment in the final comment branch are awaited without a try/catch. A GitHub API failure at this point throws out of run even though the review completed and R2 state was saved. Fix: Catch final posting/reaction errors, log them, and return a degraded result instead of failing the orchestrator.
.flue/workflows/orchestrate.ts line 217 Unhandled async errorawait addReactionToComment(...) inside /full-review is not wrapped in try/catch. If the GitHub API call fails, the exception propagates and the webhook handler returns a 500 without logging the cause. Fix: Wrap the reaction call in a try/catch, log the failure, and continue (or return a structured acted: false response) instead of letting it throw.
.flue/workflows/orchestrate.ts line 302 Unhandled async errorawait addReactionToComment(...) inside /review is not wrapped in try/catch. A reaction failure will crash the slash-command webhook with a 500. Fix: Add try/catch around the reaction call and log/handle the failure gracefully.
.flue/workflows/orchestrate.ts line 379 Unhandled async errorawait setReviewLimitIgnored(...) has no error handling. If the R2 bucket is missing or the write fails, the unhandled rejection causes a 500 response. Fix: Wrap setReviewLimitIgnored in a try/catch, return acted: false with the error, and avoid adding the 👍 reaction if persistence failed.
.flue/lib/github.ts line 235 Unescaped URL interpolation — Line 235 interpolates base and head directly into https://api.github.com/repos/${REPO}/compare/${base}...${head}. Branch or tag names containing #, ?, &, %, or spaces will malform the URL or alter the request target. Fix: Use encodeURIComponent(base) and encodeURIComponent(head) when building the compare URL.
.flue/lib/github.ts line 147 Incomplete pagination — Line 147 requests per_page=100 from the pull request files endpoint but does not follow GitHub's Link response headers. Pull requests with more than 100 changed files will silently return only the first page, missing subsequent files. Fix: Follow the Link headers to fetch all pages, or document the 100-file limit and fail explicitly when it is exceeded.
.flue/lib/github.ts line 50 Missing input validation — Line 50 calls Number(env.DOCS_FLUE_GITHUB_INSTALLATION_ID) without checking that the environment variable exists or is numeric. A missing value becomes NaN, which is passed to createAppAuth and yields a confusing downstream error. Fix: Validate that the required environment variables are defined and that the installation ID is a valid integer before constructing the auth client.
src/components/models/ModelDetailPage.astro line 632 Unchecked output schema accessmodel.schema.output.format is accessed directly even though hasSchema only verifies model.schema.input exists, and line 605 already guards against a missing model.schema.output when collecting variants. A model with an input schema but no output schema will throw a TypeError here. Fix: Use model.schema.output?.format === "binary" so missing output schemas are handled defensively.
src/components/models/ModelDetailPage.astro line 675 Unchecked output schema access — The non-variant branch repeats the same unguarded access: model.schema.output.format can fail when model.schema.output is undefined. Fix: Use model.schema.output?.format === "binary" to match the defensive pattern from line 605.
.flue/lib/github-repo-tools.ts line 119 Incorrect base64 text decoding — atob() returns a Latin1/binary string, so UTF-8 encoded files are decoded as mojibake. For example, docs files with non-ASCII characters will be returned incorrectly to the agent. Fix: Decode base64 to bytes and use TextDecoder to preserve UTF-8, e.g. new TextDecoder().decode(Uint8Array.from(atob(...).split(''), c => c.charCodeAt(0))).
.flue/lib/github-repo-tools.ts line 304 Wrong fallback value on lockfile fetch failure — When the pnpm-lock.yaml fetch fails, the tool returns transitive: true, even though it has not established whether the package is transitive. Fix: Return a neutral value such as transitive: null or false and a note that the lockfile could not be read.
.flue/lib/github-repo-tools.ts line 312 Scoped package detection bug in lockfile — pnpm v9 lockfile keys for scoped packages are quoted, e.g. '@actions/core@3.0.1':, but the regex ^\s+${escapedName}@ expects the package name immediately after whitespace and misses the leading quote. Fix: Update the regex to allow optional quotes, e.g. ^\s+['"]?${escapedName}@, and align the comment about the 'snapshots' section with the actual 'packages' section in this repo.
.flue/lib/code-review-render.ts line 113 Unescaped external input in comment metadataheadSha is interpolated directly into an HTML comment marker. If the supplied SHA contains --> or newlines, it can close or corrupt the marker and break comment parsing/reconciliation. Fix: Validate or sanitize headSha before insertion (e.g., ensure it is a 40-character hex SHA), or escape -> sequences inside the injected value.
.flue/lib/code-review-render.ts line 154 Fragile ignore key for optional line — The ignore key is built with ${f.path}:${f.line}:${f.rule}. Because line is optional, findings that legitimately lack a line number all collapse to the same path:undefined:rule key. This can cause one ignored finding to unintentionally suppress another distinct finding on the same file and rule. Fix: Use a sentinel value (e.g., 0 or a separate sentinel string) for missing line numbers in the key, or only build the ignore key when a line is present.
src/components/landing/TabBar.astro line 269 Memory leak on repeated view transitions — The window.addEventListener("resize", ...) at line 269 registers a closure that captures root, tabs, and indicator, but it is never removed. Because the inline script re-runs on every astro:page-load (line 286) and initializes a new set of tabbars, old resize listeners remain attached to window and keep detached DOM trees alive. Fix: Use an AbortController whose signal is passed to window.addEventListener("resize", listener, { signal }), and call controller.abort() on astro:before-swap (or when the root is removed), so the listener is cleaned up with each view transition.
.flue/lib/style-guide-inproc.ts line 90 Unvalidated concurrency limitwithConcurrency spawns Math.min(limit, tasks.length) workers. If limit is 0 or negative and tasks is non-empty, no workers run and the function returns an array of undefined holes. mergeStyleGuideResults then crashes iterating result.findings. The exported RunStyleGuideReviewInProcessOptions.concurrency accepts any number with no validation. Fix: Validate limit > 0 at the start of withConcurrency (throw or clamp to 1), or validate concurrency when destructuring options.
.flue/workflows/dependabot-review.ts line 185 Unhandled rejection in failure path — Inside the session.skill catch block, findExistingBotComment(token, input.number) is awaited without try/catch. If it rejects, the error escapes the failure handler, the "Review failed" placeholder is never posted, the earlier "Review in progress" comment remains stuck, and the structured failure return is skipped. Fix: Wrap the findExistingBotComment and postOrUpdateComment calls in a try/catch inside the failure handler, and log the post failure so the workflow returns the structured failure result instead of crashing.
src/components/R2LocalUploadsdiagram.astro line 12 Initial mode prop ignored — The component sets data-mode={mode} on the custom element, but connectedCallback never reads this.dataset.mode and localUploadsEnabled stays false. Passing mode="with" still renders the "without local uploads" path with an unchecked toggle. Fix: Read this.dataset.mode during connectedCallback, initialize localUploadsEnabled to true when the mode is "with", update the toggle input checked state, and call setMode(mode) so the initial render matches the prop.
.flue/lib/dependabot-review.ts line 139 Unescaped markdown metacharacters — Summary table rows interpolate pkg.name, pkg.from, pkg.to, and other freeform values directly into pipe-delimited markdown. If a package name or version contains | or backticks, the table layout breaks. The same applies to pkg.repoUsage at line 170, change at line 165, and pkg.impactReason at line 172. Fix: Escape markdown metacharacters (at least | and backticks) before interpolating these values into table rows, or wrap values in ways that prevent delimiter leakage.
.flue/lib/dependabot-review.ts line 189 Unhandled GitHub API rejectionfindExistingBotComment awaits getIssueComments without a try/catch. A network failure or GitHub error causes an unhandled rejection and aborts the workflow. Fix: Wrap the API call in try/catch and decide how to surface or recover from GitHub/network errors.
.flue/lib/dependabot-review.ts line 201 Unhandled GitHub API rejectionpostOrUpdateComment awaits updateIssueComment and postComment without error handling. If the GitHub API call fails, the rejection propagates uncaught. Fix: Add try/catch around the create/update branches so the workflow can report or retry on failure instead of crashing.
src/components/landing/BuildFromScratch.astro line 84 Incomplete API command example — The displayed curl command curl --request POST https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/images/v1 omits the required Authorization header and upload body. Pasting and running it will fail with an authentication or validation error. Fix: Make the example runnable by adding the Authorization header and a file payload, e.g. curl --request POST https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/images/v1 -H "Authorization: Bearer $API_TOKEN" --form file=@image.png.
tenant/how-to/manage-accounts.mdx line 111 Inconsistent manual-deletion resources — The caution block lists only Logpush jobs and Zero Trust Gateway configurations as resources not automatically deleted, but step 2 instructs deleting an Access organization without ever stating it is also not cascade-deleted. Fix: Add Access organization to the introductory bullet list of resources requiring manual deletion, or remove step 2 if deletion is not required.
tenant/how-to/manage-accounts.mdx line 98 Missing deletion instruction for listed resource — Logpush jobs are listed as a resource not automatically deleted and requiring removal, but no endpoint or command is provided to delete them before step 3 deletes the account. Fix: Add the appropriate Logpush deletion endpoint/command to the sequence, or remove Logpush from the list if manual deletion is not needed before account deletion.
Suggestions (12)
File Issue
public/turnstile/spin/index.md line 542 Unescaped shell interpolation in JSON payloadwidget-create.sh builds the request body by interpolating $NAME, $domains_json, and $MODE directly into a hand-quoted JSON string. Values containing double quotes, backslashes, or control characters will produce invalid JSON and cause the API call to fail. Fix: Build the payload with jq -n --arg name "$NAME" --arg mode "$MODE" --argjson domains "$domains_json" '{name:$name,domains:$domains,mode:$mode}' (or an equivalent python one-liner) so values are properly escaped.
public/turnstile/spin/index.md line 247 GET endpoint used to probe Edit permission — The auth probe performs a GET to /accounts/$account_id/challenges/widgets and reports missing_scope for Account.Turnstile:Edit. Listing widgets via GET typically requires Account.Turnstile:Read; a token granted only Edit (without Read) may fail this probe and be misreported as lacking Edit permission. Fix: Use a probe that definitively requires Edit permission (for example, a no-op POST/PATCH), or adjust the status and user-facing message to indicate that Account.Turnstile:Read is also required for the probe.
public/turnstile/spin/index.md line 445 Fragile health-check response validation — The health check greps the raw response for the literal string "ok":true. A response such as {"ok":false,"meta":{"ok":true}} would incorrectly pass, and the check cannot distinguish malformed output from a successful response. Fix: Parse the response as JSON with jq -e '.ok == true' or a python equivalent instead of grepping the raw text.
.flue/workflows/code-review-orchestrator.ts line 181 R2 list pagination is not handled — bucket.list({ prefix: ... }) during forceFullReview is used without checking truncated/cursor. If more than one page of review JSONs exists, some stale review files will not be deleted. Fix: Iterate through list results using cursor until truncated is false, or explicitly set a high limit and assert it is not truncated.
.flue/workflows/code-review-orchestrator.ts line 284 Temporary workspace diff directory is never cleaned up — Each run writes artifacts to diffs/pr-${number}/runs/${runId} in the shared workspace, but the code never deletes that directory after the review finishes or on failure. Fix: Delete the run-scoped diff directory before returning, including in error paths, to avoid unbounded workspace growth.
.flue/workflows/orchestrate.ts line 70 Unused variableconst _itemUrl = getIssueOrPullRequestUrl(eventType, body, number); assigns a value that is never used afterwards. Fix: Remove the unused variable and the getIssueOrPullRequestUrl import if it is not needed elsewhere.
src/components/WorkersVPCOverviewDiagram.astro line 373 Dead CSS copied from related component — The .mesh-pill rules (lines 373–393) and the --map-overlay/--map-strong custom properties (lines 140 and 144) are not used anywhere in this component. They appear to be leftovers copied from WorkersVPCEgressDiagram.astro, where they are actually used (mesh-pill) or in related elements (logs-card, flow-connector). Fix: Remove the unused .mesh-pill, .mesh-pill:hover, .mesh-pill code styles and the --map-overlay / --map-strong custom properties.
src/components/AgentsPlatformDiagram.astro line 5 Dead data in channel array — The name property is defined on every channels entry but is never read in the template, which only renders item.type and item.href. Fix: Remove the unused name property from each channels entry, or render it in the pill if it is meant to be visible.
src/components/AgentsPlatformDiagram.astro line 41 Dead data in tools array — The name property is defined on every tools entry but is never read in the template, which only renders item.type and item.href. Fix: Remove the unused name property from each tools entry, or render it in the pill if it is meant to be visible.
src/components/AgentsPlatformDiagram.astro line 218 Redundant CSS border color.resource-card declares border: 1px solid rgb(0 0 0 / 15%) and then immediately overrides it with border-color: var(--map-line). Fix: Combine into a single declaration: border: 1px solid var(--map-line);.
.flue/lib/github-repo-tools.ts line 69 Missing pagination for changed files — get_pr_files only requests the first page (per_page=100). Dependabot PRs that touch more than 100 files will silently omit the rest. Fix: Follow the Link response header to paginate, or document/return truncation information if you intentionally cap at 100.
.flue/lib/code-review-render.ts line 62 Incomplete Markdown table sanitizationsanitizeTableCell escapes pipes, asterisks, and newlines but not backticks. A rule, evidence string, or reviewer note containing a backtick can break the Markdown table cell formatting when wrapped in backticks by formatFile. Fix: Also escape backticks (and possibly square brackets used for links) in sanitizeTableCell.

Style Guide Review

Warnings (1)
File Issue
tenant/how-to/manage-accounts.mdx line 119 Directional words — Line adds (see below) Fix: Replace (see below) with a direct reference to the Delete account section, for example (refer to Delete account below) or remove the parenthetical.
Commands

Only codeowners can run commands. Post a comment with the command to trigger it.

Command Description
/review Runs a review now. Incremental if a prior review exists, full if not.
/full-review Re-reviews the entire PR diff from scratch, ignoring incremental history. Useful after a rebase, when you want a fresh review, or if the bot gets out of sync and reports issues that no longer exist.
/ignore-review-limit Permanently lifts the 2-review automatic limit for this PR. Future pushes will trigger reviews as normal.

@jhutchings1 jhutchings1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed @colbywhite 's feedback around code owners and approved.

@jhutchings1 jhutchings1 requested a review from colbywhite June 24, 2026 15:06
@jhutchings1 jhutchings1 enabled auto-merge (squash) June 24, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants