Skip to content

The pulumi member

llms.txt lists every page for an agent

The pulumi member kind is supplied by @intentius/chant-lexicon-pulumi (workspace-kinds.json). It moved there from the terraform lexicon in #4453 and kept its name.

The ruling on #4476 (2026-10-25) widened the #4450 ruling’s preview and refresh --preview-only to the list below, and the list is the whole of what chant runs for a Pulumi member:

ReadStock memberMember on plungeChanges the stack
graphnone; the export in the index store, else the checked-in stack-export.jsonthe sameno
export after an applypulumi stack export --stack <name>, from the pulumi-up apply onlyplunge stack export --stack <name>, the sameno
change setpulumi preview --jsonplunge preview --jsonno
driftpulumi refresh --preview-only --json --stack <name> --target <urn>plunge drift --resource <urn> --stream --stack <name>no
applied outputspulumi stack output --json, --show-secrets absent or =falseplunge stack output --json, the sameno
planned outputsthe change set’s previewthe sameno
engine factsnoneplunge graph --json --stack <name>, or the checked-in engine-graph.json (A member on plunge)no
check stagepulumi install, then pulumi about --jsonplunge install, then plunge about --jsonno

runAllowedPulumi and runAllowedPlunge refuse any other argv before it reaches the binary, and stack output with secrets shown is refused too. With CHANT_PULUMI_APPLIED_OUTPUTS=export (or applied: "export" on the source), the applied outputs are read from the checked-in export and nothing runs. Each command runs in the member’s directory against its selected stack, except the drift read, which names the stack with --stack (#4496): pulumi.member.stack when the config sets it, else the stack the checked-in export’s URNs name. A checked-in member with no selected stack can still be refreshed.

chant ci check runs three steps for each pulumi member after the ci.hooks.check hooks (#4633), so plywood check runs them too:

StepRunsFails when
pulumi-installpulumi install in the member’s directory (plunge install on plunge)the install exits non-zero, or the member has no Pulumi.yaml
stack-confignothing; each Pulumi.<stack>.yaml is read against Pulumi.yaml’s config: blocka key with a type and no default is unset, a value does not read as its type, or a secret: true key is in plain text
pulumi-aboutpulumi about --json, with --stack when the member declares oneit prints no JSON

The stacks are the project’s stack files, in stackConfigDir when Pulumi.yaml sets it, plus the member’s declared stack. The steps run with the check job’s environment, which holds no forge token and no cloud credential, so neither command can write a stack. runCheckPulumi refuses any other argv. Each step’s result is in check-hooks.json under steps, and a failed one names the member and stack and makes the check exit 1.

FieldValue
probea Pulumi.yaml in the directory
member filePulumi.yaml, checked for name and runtime
precedence310
graphthe pulumi lexicon, with pulumi.member set to the member’s directory and name

The graph is the stack’s pulumi stack export. After each apply through the pulumi-up capability, chant writes the export to its index store (refs/chant/snapshots by default) with every secret blinded, and the graph reads it from there. A stack applied outside chant checks the export in beside Pulumi.yaml as stack-export.json instead. A node’s id is <member>/<qualified type>::<name>, the URN after its stack and project, so urn:pulumi:dev::shop::aws:s3/bucketV2:BucketV2::assets in member infra is infra/aws:s3/bucketV2:BucketV2::assets. The change set and the drift records put a resource on the same node by the same rule (@intentius/chant/pulumi-urn). Edges come from dependencies and parent. Providers and the root stack have no node, and inputs and outputs are not copied, since an export can hold secrets.

With no export, or one that is not JSON, the member has no nodes and the graph’s meta.pulumiMember is { exported: false, reason }.

The member’s part of the change set is pulumi preview --json, planner pulumi, with the checked-in export as the last deployment. One entry per URN: create, update (in place), delete, a replace as one replace entry, same as no-op, read and refresh as read. An input that holds Pulumi’s unknown value is unknown: true, and a secret is sensitive: true with neither value. Resources a program registers inside an apply() the preview could not run are marked mayAppear when they sit under a component, or when the checked-in export holds them and the preview has no step for them. A resource registered inside an apply() directly under the stack is not marked: with no export holding it, it is not in the change set, and when the export holds it and its input is unknown, the preview prints a delete that the change set keeps as a plain delete. Nothing in a Pulumi preview tells that apart from a real removal (#4497). The full table is under Pulumi plans.

The pull request note gets a tips section for each pulumi member whose change set shows one of these patterns (#4634). Each row starts with the tip’s id, and a product’s note ends with its own command for the fix, such as plywood tips rename-without-aliases.

TipFound when
rename-without-aliasesthe preview deletes a resource and creates one of the same type whose name has the same stem (assets and assets-v2), or the same name under another parent
replace-on-namea replace that creates before it deletes is forced by a name input (name, <x>Name, bucket)
unknown-in-applyan entry may register at up, because the preview could not list it

A rename with aliases previews as same or update under the new URN, so a delete and a create of one type and stem in one preview is the rename without them. The stem is the name up to its first -, _, ., : or /. The tips are read from the change set alone, so stack config and protect give none.

chant workspace drift --member <member> [--resource <urn>] [--stream] runs chant lifecycle drift in the member’s reader project, which calls the lexicon’s readDrift. A stock member is read with one pulumi refresh --preview-only --json --target <urn> per resource in its export; with no export, one untargeted refresh. same is in-sync, update is drifted with a path per detailedDiff key, delete is missing, and a target the refresh did not reach is not-observed. See the drift record reference.

A member runs on plunge when its config says engine: "plunge", or when Pulumi.yaml has a backend.url starting with plunge. Its drift is plunge drift --resource <urn> --stream per resource. A member on plunge covers the rule and the commands.

Another member reads a Pulumi member’s outputs as parameters with core’s member-outputs verb (see Wiring components). The consumer lists pulumi among its lexicons.

OutputsSource
appliedpulumi stack output --json --show-secrets=false, or the checked-in export with CHANT_PULUMI_APPLIED_OUTPUTS=export
plannedpulumi preview --json --show-sames: the export’s outputs when it plans nothing to apply; when it plans a change, the outputs it leaves alone keep the export’s value and the rest are unknown

A real preview prints the stack’s step with no outputs (recorded against the plunge corpus, #4475), so chant cannot read a planned value off it. Instead each output is traced by value to the export’s resource properties that hold it (#4635). An output is unknown when one of those resources is replaced or deleted, or updated with a diff naming the property, or when it is held only by a property the provider computes on an update. It keeps the export’s value when its resources are unchanged, or when it is an id, arn or urn, or an input the diff leaves alone. An output no property holds, such as one the program builds from a template string, is unknown. In the recorded VPC replace, vpcId, securityGroupId, vpcParameterName and workerQueueUrls are unknown and the other eight outputs keep their values. pulumi-up’s plan lists each verdict and the property behind it on its artifact under plannedOutputs.

A stack that reads another through StackReference (#4547) plans now when every output it reads keeps its value. When one is unknown, the reading stack is a hole naming the read, the note lists it under “Known only after apply”, and the stack is previewed again after the producer’s up. A planned output that is unknown leaves the consumer planned without it, the change set has a hole on <member>#outputs naming it, and on merge the consumer waits for the member’s apply and is planned again. The member part’s pulumi-preview digest is the plan digest, so a preview that moves after review moves the consumer’s digest. Secret outputs are left out of both reads.

A project can set pulumi.member itself to read a member’s graph into its own chant build:

export default {
lexicons: ["pulumi"],
pulumi: { member: { dir: "./infra", label: "infra" } },
} satisfies ChantConfig;

dir is resolved against the project root. engine is pulumi or plunge and defaults to what Pulumi.yaml says. stack is the stack the drift read passes as --stack, and defaults to the one the export’s URNs name. The build runs these checks:

IdKindReports
PUL101lintpulumi.member.dir written as an absolute path
PUL001post-syntha member read with no nodes, with the reason
PUL002post-synthan export that records pending operations from an interrupted update
PUL003 to PUL015post-synthwhat else the export says about the stack: plaintext secrets, half-finished replacements, the wrong stack or project, a damaged file

The stack export lists every check with its fix, and chant audit gives each a title and a remediation from the lexicon’s audit catalog.

PulumiWatchOp (#4494) previews one member and reads its drift, on a schedule or with chant run. It runs the commands in the table above and never applies. Watching a member has its fields, phases and outcomes.

chant