#399 @swamp/gcp/cloudresourcemanager/folders: create method has 5 blocking bugs (missing parent in body, LRO detection, post-LRO state, idempotency, projectId requirement)
Opened by thescarletmanuka · 5/21/2026· Shipped 5/21/2026
Summary
The @swamp/gcp/cloudresourcemanager/folders model is non-functional in its current shape — create cannot produce a folder, even with valid global args. Working through it surfaces 5 separate issues, 4 in the folders model itself and 1 in the shared _lib/gcp.ts. I patched the bundle to get past each one; all 5 patches are reproducible below.
Filed as a separate issue from FR #398, which is scoped to "missing methods." This one is scoped to "methods exist but are broken."
Affected
@swamp/gcp/[email protected]—foldersmodel- The shared
models/_lib/gcp.tsused by all@swamp/gcp/*extensions (issue #5 below — likely affects every model that callscreateResourceagainst an org-scoped resource)
The other models in @swamp/gcp/cloudresourcemanager (projects, tagKeys, …) use the same createResource helper and the same auto-generation template, so issues 2 / 3 likely affect them too. I haven't hit them in the wild yet — folder creation was where I started.
Reproduction
swamp extension pull @swamp/gcp/cloudresourcemanager
swamp model create @swamp/gcp/cloudresourcemanager/folders folder-shared
# Edit YAML to set:
# globalArguments:
# parent: organizations/734088908573
# displayName: shared
swamp model method run folder-shared createExpected: a folder shared/ is created under organizations/734088908573 and its state stored.
Actual (in order, as each fix unblocks the next):
1. getApplicationDefaultCredentials throws on org-scoped ops
error: Could not determine GCP project ID. Set GCP_PROJECT or GOOGLE_CLOUD_PROJECT, or run: gcloud config set project <project-id>The shared helper throws if no project is configured, but folder/org operations don't need one — the URL is https://cloudresourcemanager.googleapis.com/v3/folders with body {parent: organizations/...}, no project in the path or quota header from this code path.
Workaround patch: return { projectId: "", accessToken } instead of throwing. Same patch needed for project creation, custom-role creation, and any other org-scoped op.
2. POST body omits parent
After bypassing #1:
error: Create failed (403): {"error": {"code": 403, "message": "The caller does not have permission"}}The generated create method builds the request body with displayName, name, tags — but never parent. The POST then goes to /v3/folders with no parent, and GCP rejects with 403 (because there's no resource to check IAM against; the caller can't be authorized for a target that doesn't exist).
Reference (from the unbundled source cloudresourcemanager/models/folders.ts):
const body: Record<string, unknown> = {};
if (g["displayName"] !== undefined) body["displayName"] = g["displayName"];
if (g["name"] !== undefined) body["name"] = g["name"];
if (g["tags"] !== undefined) body["tags"] = g["tags"];
// missing: if (g["parent"] !== undefined) body["parent"] = g["parent"];parent is declared in GlobalArgsSchema (and even marked "Required" in the description), but the create method never reads it into the body.
3. isLongRunningOperation misses v3 LROs without a done field
After bypassing #2 (folder gets created in GCP — confirmed via gcloud), the post-create state-fetch fails:
error: Missing required path parameter: nameThe v3 folder create's initial response is:
{ "name": "operations/create_folder.global.<id>", "metadata": {...} }— no done field. The detector requires both done and name:
if ("done" in response && "name" in response && response.name.includes("operations/")) return true;So isLongRunningOperation returns false, the LRO branch is skipped, and the readiness loop downstream calls buildUrl(baseUrl, readConfig, params) with readConfig.path = "v3/{+name}" and params.name empty → the "Missing required path parameter: name" throw.
Fix: detect by name: "operations/..." alone (or in addition to the current done && name check).
4. Post-LRO completion doesn't extract the resource name
After fixing #3 so the LRO is detected, the code polls, gets operation.done = true, then needs to GET the created resource for state. But the GET uses the same params from the create call — which never had params.name populated (the new folder's name was only known after the LRO completed).
createResource's LRO-complete branch needs to either:
- prefer
operation.responsedirectly when present (v3 LROs put the resource here), or - extract
operation.response.nameintoparams.namebefore the post-LRO GET.
The shape of operation.response for v3 folder create, captured live:
{
"@type": "type.googleapis.com/google.cloud.resourcemanager.v3.Folder",
"name": "folders/981118018507",
"parent": "organizations/...",
"displayName": "diag-test",
"state": "ACTIVE",
"createTime": "...",
"updateTime": "...",
"etag": "..."
}This is the standard v3 LRO completion pattern — should be the cleanest case to handle.
5. create not idempotent
After fixing #4, the first run succeeds. The second run (or any retry after partial failure) fails with:
error: Operation failed: Folder reservation failed for parent [organizations/...], folder [] due to constraint: The folder operation violates display name uniqueness within the parent.GCP's "already exists" wording for folders is unusual — not 409/ALREADY_EXISTS, but a 200 OK on the LRO with an error in operation.error saying "display name uniqueness within the parent."
For a workflow-driven bootstrap pattern (swamp workflow run to reproduce state), every create ought to be idempotent — succeed if the resource already matches the spec. Either:
- handle the 409-ish case in the model's create (search-by-displayName+parent fallback), or
- expose a higher-level
ensuremethod that does the search-then-create dance.
Workaround in use
5 patches to .swamp/bundles/0376cd21/folders.js covering issues 1–5. Patches are lost on swamp extension install. The patches are committed (for documentation) at https://github.com/sil-rev79/infrastructure on branch swamp (commit 9c921cb).
Why this matters
Workstream-driven infra bootstrap means running the workflow against a fresh org / blue-green re-build / disaster recovery scenario. Every model on the path needs to be functional and idempotent. Issues 1–4 block any usage; issue 5 blocks reproducible bootstrapping.
The autogenerator presumably should be the layer that fixes 2 & 4 (since they affect every "create" with LRO + resource-bearing-response pattern), and 1 & 3 should be fixed in the shared lib.
Environment
- swamp:
20260515.175436.0-sha.2f8e98af @swamp/gcp/[email protected]- Linux x86_64
Upstream repository: https://github.com/systeminit/swamp-extensions
Environment
- Extension:
@swamp/gcp/[email protected] - swamp:
20260520.150010.0-sha.47868db6 - OS:
linux(x86_64) - Deno:
2.7.14+19bd3d8 - Shell:
/bin/bash
Shipped
Click a lifecycle step above to view its details.
thescarletmanuka commented 5/21/2026, 2:11:00 AM
Update: same patches now applied proactively to @swamp/gcp/cloudresourcemanager/projects in the same bundle (.swamp/bundles/0376cd21/projects.js).
Issues 1, 3, 4 from the original report (projectId requirement, LRO detection, post-LRO state-extract) are shared via the bundle's inlined _lib/gcp.ts code — already fixed for the whole bundle. So the patches still required per-model for projects are:
- Issue #2 (parent missing from body) — same as folders. The generated code builds
bodyfromdisplayName,labels,projectId,tags— but never fromparent. Withoutparent, GCP can't determine which folder/org to create the project under and rejects. Added:if (g["parent"] !== void 0) body["parent"] = g["parent"]; - Issue #5 (idempotency) — same shape as folders but with project-specific recovery. On 409 / ALREADY_EXISTS / "already in use" the patch falls back to
GET /v3/projects/{projectId}to retrieve the existing project's state. Projects are identified byprojectIdwhich is a globally-unique string the user supplies, so the recovery path is simpler than the folder one (no list+filter needed).
GCP project create's "already exists" wording in my testing: 409 with "projectId" ... is already in use — different from folders' "display name uniqueness." Regex broadened accordingly: /\(409\)|ALREADY_EXISTS|already exists|already in use/i.
The tagKeys, tagValues, and liens models in the same bundle haven't been exercised yet but will likely hit issues 2 and 5 too once they are.
This is a strong signal that fix #2 (parent in body) belongs in the auto-generator template, not per-model. The generator already declares parent in GlobalArgsSchema (marked "Required" even) but doesn't read it into the body for resources where parent appears in the request payload (vs. the URL path).
Patches committed to https://github.com/sil-rev79/infrastructure branch swamp.
Sign in to post a ripple.