Skip to content
Back to skills

Sg Writing Stackguardian Workflow Group Terraform

ASecurity

Use when writing, reviewing or debugging Terraform/OpenTofu HCL for StackGuardian workflow groups (stackguardian_workflow_group) — nested groups, group ids vs display names, referencing a group from workflows, policies, chaining or runtime references, importing existing groups — or when a group create fails with "Workflow Group ID not unique", a nested group fails to create, a group has a random suffix, or destroy fails with "Workflow Group not empty".

  • 12 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 11, 2026
ai-agentsgcpterraformdebugginggitapi

Works with

  • api
  • mcp

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned November 11, 2026

npx -y skills add StackGuardian/stackguardian-mcp --skill sg-writing-stackguardian-workflow-group-terraform --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sg Writing Stackguardian Workflow Group Terraform?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Sg Writing Stackguardian Workflow Group Terraform
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/stackguardian-sg-writing-stackguardian-workflow-group-terraform/badge)](https://www.skillsdirectory.com/skills/stackguardian-sg-writing-stackguardian-workflow-group-terraform)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: sg-writing-stackguardian-workflow-group-terraform
description: Use when writing, reviewing or debugging Terraform/OpenTofu HCL for StackGuardian workflow groups (stackguardian_workflow_group) — nested groups, group ids vs display names, referencing a group from workflows, policies, chaining or runtime references, importing existing groups — or when a group create fails with "Workflow Group ID not unique", a nested group fails to create, a group has a random suffix, or destroy fails with "Workflow Group not empty".
---

# Writing StackGuardian workflow group Terraform

## Overview

A workflow group is a folder for workflows and stacks and the usual unit of access control.
Groups nest with `/`. Provider `StackGuardian/stackguardian` **1.12.3**; setup (source address,
`api_uri`) is in **sg-writing-stackguardian-workflow-terraform**. Facts marked **(live)** were
reproduced against a real org.

## Core pattern

```terraform
resource "stackguardian_workflow_group" "platform" {
  id            = "platform"                 # set it: the route id everything else uses
  resource_name = "Platform"                 # display name, free text
  description   = "Shared platform infrastructure" # <= 255
  tags          = ["platform"]               # <= 10; never tags = [] on a group (inconsistent result)
}

resource "stackguardian_workflow_group" "networking" {
  id            = "${stackguardian_workflow_group.platform.id}/networking" # nested: "platform/networking"
  resource_name = "Networking"               # leaf display name (live)
}

resource "stackguardian_workflow_git" "vpc" {
  workflow_group_id = stackguardian_workflow_group.networking.id # bare id, full path when nested
  # ...
}
```

## Discover existing groups first (StackGuardian MCP)

If the StackGuardian MCP server is connected, list groups before creating or referencing one:
`search_orchestrator_resources(resourceTypes=["WORKFLOW_GROUP"], searchQuery="<name>",
fields=["ResourceId","ResourceName","SubResourceId","ParentId","OrgId"])`.

- **`SubResourceId`** is the full path: `/wfgrps/PROD/GCP-4jc2fd3k` → `workflow_group_id =
  "PROD/GCP-4jc2fd3k"`, import id `PROD/GCP-4jc2fd3k`, policy scope as-is. `ResourceId` is only the
  leaf; `ResourceName` is the display name (ids often carry random suffixes).
- An id you plan to create that already exists → import it instead (create would 409).
- `search_workflows(parentId="/orgs/<org>/wfgrps/<path>")` shows what lives in a group before you
  delete or re-scope it.
- Check `OrgId = "/orgs/<STACKGUARDIAN_ORG_NAME>"`; use read tools only. Without MCP: ask for the id and
  add a `data "stackguardian_workflow_group"` existence check.

## Ids

- `id` set → used verbatim (slug `^[A-Za-z0-9_-]+$`; a nested id is `parent/child`). Changing it
  replaces the group **and every workflow referencing it**: `workflow_from_template` workflows are
  destroyed and recreated (SG-managed state and run history lost), `workflow_git` ones end in
  *inconsistent result* and keep the old group non-empty. Treat group ids as permanent.
- `id` omitted → derived from `resource_name`: kept as-is when slug-shaped, otherwise lowercased
  with a random suffix (`"Platform Team"` → `platform-team-x7k2lm9q4n1d`). Always set `id` when the
  name is not a slug.
- Nested without `id`: `resource_name = "${parent.id}/networking"` also works, but the full path
  becomes the display name (live). Prefer the `id` + leaf `resource_name` form above. Without `id`,
  any `/` in `resource_name` means nesting (`"Dev/Test"` looks for a parent `Dev` → 409); with `id`
  set, the display name may contain `/`.
- The parent must exist first; referencing `parent.id` orders creation. The dashboard stops at a
  depth of 10.
- Groups created in the dashboard often have ids like `gitops-y8pf2mb1` (name + random suffix):
  look them up by id, never assume id = name.

## How other resources reference a group

| Where | Form |
|---|---|
| `workflow_group_id` on workflows | `stackguardian_workflow_group.x.id` → `platform/networking` (resource, or a literal/local full path — never `data.<group>.id` for a nested group, see below) |
| `stackguardian_policy.enforced_on` | `"/wfgrps/${stackguardian_workflow_group.x.id}"` — covers nested groups, stacks and workflows below; no trailing slash |
| `mini_steps.wf_chaining.*.workflow_group_id` | bare id (`platform/networking`) |
| `${workflow::<group>.<wf>.outputs...}` run-time reference | bare id, `/` kept; in HCL escape the `$`: `"$${workflow::platform/networking.vpc.outputs.vpc_id.value}"` |
| `data "stackguardian_workflow_outputs"` | `workflow_group = stackguardian_workflow_group.x.id` |
| role `allowed_permissions` paths | group **ids** (full path when nested), not `/wfgrps/...` and not display names; the values are regexes full-matched against the group path, so `["platform"]` does not cover `platform/networking`: grant both `platform` and `platform/.*` (the form StackGuardian's own Terraform modules use, from code), or list the children. The lists are positional: `<wf>`, `<wfRun>` … lists in the same permission need one entry per `<wfGrp>` entry (pad with `".*"`) |
| import id | `platform/networking` |

A workflow whose `workflow_group_id` names a group that does not exist is **still created**: the
server auto-creates a *top-level* group named after the last path segment (description
`Automatically created by SG when creating a workflow`, tags `sg-created`, `workflow`), and for a
nested path the workflow is stored under a parent path that has no group. If a top-level group of
that name already exists, the create fails with 500 `Workflow Group does not exist and cannot be
created.` Always reference the group resource (or a literal full path you know exists).

## Lifecycle

| Action | Behaviour |
|---|---|
| Create with an id that already exists | 409 `Workflow Group ID not unique` (live). The provider docs claim it adopts the group — it does not. Import it instead |
| Create that fails with a **400** (description > 255, 11 tags, non-slug `id`, reserved name) | the provider reads the group named by `resource_name` and, if one exists, silently **adopts** it into state — a later destroy deletes a group you never created. Fix the 400 cause; check the plan when names may collide |
| Reusing the id of a group moved in the dashboard | 409 `A resource used to exist with the same id but was moved to …` — the provider cannot pass `replace-moved-resource`; pick another id |
| Change `resource_name` | in-place rename |
| Change `id` | replace (the old group must be empty to delete) |
| Omit `description`/`tags` while changing anything else | they are **cleared** (Optional+Computed without state carry-over; live plan) — removing them alone shows no diff until some other attribute changes. Keep them declared when the group has values; to clear tags, omit them and change another attribute in the same apply |
| `tags = []` | *Provider produced inconsistent result after apply*, group tainted (live) — omit `tags` for a group without tags. `description = ""` is fine |
| Delete | only when empty: `Workflow Group not empty` (400) while any workflow, stack or child group remains. Terraform orders children first within one config (live); workflows created outside Terraform block it. Right after deleting the children the check is eventually consistent — retry |
| Move a workflow between groups | no in-place move: `terraform apply -replace` (new workflow, managed state lost) or move it in the dashboard and re-import at the new address (workflow skill, lifecycle §4) |
| Group deleted outside Terraform | dropped from state on refresh and silently re-created on the next apply |
| Import | `terraform import stackguardian_workflow_group.networking platform/networking`, or an `import` block + `terraform plan -generate-config-out=generated.tf` to get its current name/description/tags written for you |
| Read an existing group | `data "stackguardian_workflow_group" "x" { id = "platform/networking" }` fails at plan with `Unable to read workflow group.` when it does not exist — use it as an **existence check**. It returns `description` and `tags`, but its `id` is only the **leaf** (`networking`) for nested groups and `resource_name` is null (live). Build references from the path you looked up (`local.net = "platform/networking"`), not from `data.x.id` |

Limits: `resource_name` ≤100 (reserved names such as `wfgrps`, `policies`, `secrets`, `logs`,
`outputs` are rejected; the check skips `id`, so `id = "listall"` is created but unreachable at
its own route — from code), description ≤255, tags ≤10 (the dashboard strips every non-word character — `-`, `.`, `:` — from tags it edits;
Terraform keeps them). The server has no nesting-depth limit; the dashboard stops at 10.

## Common mistakes

| Mistake | What happens |
|---|---|
| `resource_name = "Platform Team"` and no `id` | Random-suffixed id; every reference by name breaks |
| `workflow_group_id = "/wfgrps/platform"` | Wrong: bare id only (`/wfgrps/` is the policy form) |
| `enforced_on = ["/wfgrps/platform/"]` | Trailing slash: matches nothing |
| `enforced_on = ["/wfgrps/platform/wfgrps/networking"]` | Not a scope (that shape is only the child-create URL / dashboard tree key): use `/wfgrps/platform/networking` |
| `workflow_group_id = data.stackguardian_workflow_group.net.id` (nested) | Resolves to the leaf `networking`: wrong/auto-created top-level group (live data-source behaviour) |
| Hard-coding `workflow_group_id = "platfrom"` | Server auto-creates a junk group `platfrom` |
| Nested group before its parent exists | 409 `Workflow Group does not exist` — reference `parent.id` |
| Deleting a group that still has dashboard-created workflows | 400 `Workflow Group not empty` |

Worked example (validated with `terraform validate` and `tofu validate`, applied live):
`examples/groups.tf`.

Files in this skill

  • SKILL.md9.7 KB
  • examples/groups.tf1.9 KB
  • examples/versions.tf587 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in ai-agents →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.