Skip to content

sc destroy: SIGSEGV (nil-pointer) in DestroyChildStack on a never-deployed child stack #367

Description

@simple-container-forge

Summary

sc destroy panics with a nil-pointer dereference (SIGSEGV, exit 2) when invoked against a child stack that was never deployed for the given env. It should no-op cleanly (there is nothing to destroy).

Location

pkg/clouds/pulumi/provisioner.go(*pulumi).DestroyChildStack, around line 109:

childStack := toChildStack(parentStack, params.StackParams)
s, err := p.selectStack(ctx, cfg, childStack)
if err != nil {
    return errors.Wrapf(err, "failed to get child stack %q", childStack.Name)
}
program := p.deployStackProgram(childStack, params.StackParams, parentStack.Name, s.Ref().FullyQualifiedName().String())  // SIGSEGV: s is nil for a never-deployed child

For a never-deployed child stack, selectStack returns a nil/empty stack with no error, and s.Ref().FullyQualifiedName().String() dereferences nil → panic. The deploy path avoids this via initChildStackForDeploycreateStackIfNotExists; DestroyChildStack has no equivalent existence guard.

Stack trace (real failure, CLI 2026.7.4)

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x38]
goroutine 1 [running]:
github.com/simple-container-com/api/pkg/clouds/pulumi.(*pulumi).DestroyChildStack(...)
	pkg/clouds/pulumi/provisioner.go:109 +0x289
github.com/simple-container-com/api/pkg/provisioner.(*provisioner).Destroy(...)
	pkg/provisioner/destroy.go:21 +0x1de
github.com/simple-container-com/api/pkg/cmd/cmd_destroy.NewDestroyCmd.func1(...)

Repro

Deploy an isolated stack with a subset of services (e.g. services=baas), then run sc destroy against the same env for a never-deployed service. Real failing run: simple-container-com/forge run 29758451168 (forge_wf_id=s4baas) — Destroy forge-baas succeeded; the other 6 service jobs each panicked.

Impact

  • Single-service isolated smokes report a false destroy-failure (the deployed service IS destroyed; only never-deployed siblings panic). 6 of the last 8 destroy-isolated-stack.yml runs were red for this reason.
  • The stale-stack sweeper re-panics every sweep (it dispatches destroy without a services filter).

Suggested fix

Add an existence guard after resolving the child stack and return nil (no-op) when absent, mirroring initChildStackForDeploy/createStackIfNotExists's notion of "absent" so deploy and destroy agree:

s, err := p.selectStack(ctx, cfg, childStack)
if err != nil {
    return errors.Wrapf(err, "failed to get child stack %q", childStack.Name)
}
if s == nil /* or s.Ref() is zero/empty, per selectStack's not-found contract */ {
    p.logger.Infof("child stack %q not found in env; nothing to destroy (no-op)", childStack.Name)
    return nil
}

Workflow-side mitigation shipped

forge PR simple-container-com/forge#683 guards destroy-isolated-stack.yml so the specific panic is treated as an idempotent no-op (success) — makes every caller correct today, independent of a CLI release. This issue tracks the underlying CLI defect so that guard can eventually be retired.

Filed from DevOps workflow run 9738f311-05bf-42d5-9976-4863c516e819.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions