{"record":{"id":"db0e21b282f54915","repo":"hashicorp/terraform","slug":"expected-exactly-one-provider-requirement-for-the-db0e21","errorCode":null,"errorMessage":"expected exactly one provider requirement for the destination state store provider, got %d","messagePattern":"expected exactly one provider requirement for the destination state store provider, got (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/command/state_migrate.go","lineNumber":503,"sourceCode":"\t// The state migrate command does not support the -lockfile=readonly flag\n\t// This flag is specific to the init command, and can only take \"\" or \"readonly\" as values.\n\t// As state migrate doesn't take this flag, we can safely set it to \"\" here.\n\tflagLockfile := \"\"\n\n\treturn c.Meta.saveDependencyLockFile(previousLocks, newLocks, c.incompleteProviders, flagLockfile, view)\n}\n\n// getSingleProvider is used to download the source and/or destination state store providers during a state migration.\n// Download of the up to 2 providers is kept separate due to:\n// - Potential for downloading different versions of the same provider\n// - Need to keep the locks separate for source and destination providers; destination providers are added to the dependency lock file.\nfunc (c *StateMigrateCommand) getSingleProvider(ctx context.Context, stateStore *configs.StateStore, reqs providerreqs.Requirements, locks *depsfile.Locks, upgrade bool, location string, view views.StateMigrate) (output bool, resultingLock *depsfile.Locks, trust ProviderTrust, authResult *getproviders.PackageAuthenticationResult, diags tfdiags.Diagnostics) {\n\tctx, span := tracer.Start(ctx, \"install state migration \"+location+\" provider\")\n\tdefer span.End()\n\n\t// We expect to download only one provider\n\tif len(reqs) != 1 {\n\t\tpanic(fmt.Sprintf(\"expected exactly one provider requirement for the destination state store provider, got %d\", len(reqs)))\n\t}\n\n\t// Check for legacy provider addresses.\n\tfor providerAddr := range reqs {\n\t\tif providerAddr.IsLegacy() {\n\t\t\tdiags = diags.Append(tfdiags.Sourceless(\n\t\t\t\ttfdiags.Error,\n\t\t\t\t\"Invalid legacy provider address\",\n\t\t\t\tfmt.Sprintf(\n\t\t\t\t\t\"This configuration or its associated state refers to the unqualified provider %q.\\n\\nYou must complete the Terraform 0.13 upgrade process before upgrading to later versions.\",\n\t\t\t\t\tproviderAddr.Type,\n\t\t\t\t),\n\t\t\t))\n\t\t}\n\t}\n\tif diags.HasErrors() {\n\t\treturn false, nil, Invalid, nil, diags\n\t}","sourceCodeStart":485,"sourceCodeEnd":521,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/command/state_migrate.go#L485-L521","documentation":"This panic fires at the top of getSingleProvider when len(reqs) != 1. getSingleProvider downloads exactly one state-store provider (source or destination) during migration, so the caller must hand it a Requirements map of size 1; any other size is a programming contract violation.","triggerScenarios":"Called twice during state migration (once for source, once for destination provider). Panics if getDestinationStateStoreProviderRequirements or the source-side equivalent returns 0 or >1 requirements. Because errors 1003/1004 already guard the destination path, this is most likely reached via the source-side caller passing the wrong Requirements object, or a future caller misusing the helper.","commonSituations":"A maintainer adds a new caller of getSingleProvider that builds a multi-provider Requirements map (e.g. reusing the full config requirements instead of the filtered single-provider map). Not user-triggerable through normal CLI flags.","solutions":["Audit every caller of getSingleProvider — each must pass a Requirements map containing exactly one provider entry (the source or the destination).","Re-run state_migrate_test.go to confirm the existing source/destination flows still pass single-element maps.","If you need multi-provider download, write a new helper rather than loosening this invariant."],"exampleFix":"// before — caller passes full config requirements\nreqs := allConfigRequirements // size N\noutput, lock, trust, auth, diags := c.getSingleProvider(ctx, store, reqs, locks, upgrade, loc, view)\n// after — caller passes the filtered single provider\nsingle := providerreqs.Requirements{srcProvider: reqs[srcProvider]}\noutput, lock, trust, auth, diags := c.getSingleProvider(ctx, store, single, locks, upgrade, loc, view)","handlingStrategy":"validation","validationCode":"// Enforce the single-element contract at the call boundary.\nif len(reqs) != 1 {\n  return fmt.Errorf(\"getSingleProvider requires exactly 1 requirement, got %d\", len(reqs))\n}","typeGuard":"// n/a — length check","tryCatchPattern":null,"preventionTips":["Document the size-1 invariant on getSingleProvider's godoc.","Add a unit test asserting every caller passes a 1-element map.","Prefer returning (Requirements, error) over panic for internal helpers."],"tags":["terraform-cli","state-store","state-migrate","panic","invariant","required-providers"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}