{"record":{"id":"64070f54a0703911","repo":"unionlabs/union","slug":"upgradetotrigger-is-not-of-type-string","errorCode":null,"errorMessage":"upgradeToTrigger is not of type string","messagePattern":"upgradeToTrigger is not of type string","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"uniond/cmd/uniond/cmd/testnet.go","lineNumber":236,"sourceCode":"\n\tnewValAddr, ok := appOpts.Get(server.KeyNewValAddr).(bytes.HexBytes)\n\tif !ok {\n\t\tpanic(\"newValAddr is not of type bytes.HexBytes\")\n\t}\n\targs.newValAddr = newValAddr\n\tnewValPubKey, ok := appOpts.Get(server.KeyUserPubKey).(crypto.PubKey)\n\tif !ok {\n\t\tpanic(\"newValPubKey is not of type crypto.PubKey\")\n\t}\n\targs.newValPubKey = newValPubKey\n\tnewOperatorAddress, ok := appOpts.Get(server.KeyNewOpAddr).(string)\n\tif !ok {\n\t\tpanic(\"newOperatorAddress is not of type string\")\n\t}\n\targs.newOperatorAddress = newOperatorAddress\n\tupgradeToTrigger, ok := appOpts.Get(server.KeyTriggerTestnetUpgrade).(string)\n\tif !ok {\n\t\tpanic(\"upgradeToTrigger is not of type string\")\n\t}\n\targs.upgradeToTrigger = upgradeToTrigger\n\n\t// validate  and set accounts to fund\n\taccountsString := cast.ToString(appOpts.Get(flagAccountsToFund))\n\n\tfor _, account := range strings.Split(accountsString, \",\") {\n\t\tif account != \"\" {\n\t\t\taddr, err := sdk.AccAddressFromBech32(account)\n\t\t\tif err != nil {\n\t\t\t\treturn args, fmt.Errorf(\"invalid bech32 address format %w\", err)\n\t\t\t}\n\t\t\targs.accountsToFund = append(args.accountsToFund, addr)\n\t\t}\n\t}\n\n\t// home dir\n\thomeDir := cast.ToString(appOpts.Get(flags.FlagHome))","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/unionlabs/union/blob/031785bb6dc6b957c624e62bc64c184409c97d7b/uniond/cmd/uniond/cmd/testnet.go#L218-L254","documentation":"The last of getCommandArgs' assertions: server.KeyTriggerTestnetUpgrade must be present as a string (the upgrade name the local testnet should trigger, empty when unused). Missing option or wrong type panics. Note the next option, accounts-to-fund, is read with cast.ToString instead — showing the tolerant pattern these four panics do not use.","triggerScenarios":"Programmatic testnet app construction without setting KeyTriggerTestnetUpgrade; a custom command dropping the flag binding; the value supplied as a non-string type in appOpts.","commonSituations":"Tests embedding the testnet runner; wrappers around the testnet command; option maps built from JSON where the field is absent or typed differently.","solutions":["Run the standard `uniond testnet` command so all server.Key* flags are bound","When constructing appOpts yourself, set an explicit value even if empty: viper.Set(server.KeyTriggerTestnetUpgrade, \"\")","Check for typos/casing in option keys — appOpts.Get on a missing key returns nil and trips the assertion","Fork-friendly change: read with cast.ToString and validate, mirroring the accounts-to-fund line below it"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"vpr.Set(server.KeyTriggerTestnetUpgrade, \"\") // empty string is valid; absence (nil) panics","typeGuard":"func optTrigger(appOpts servertypes.AppOptions) (string, bool) {\n\tv, ok := appOpts.Get(server.KeyTriggerTestnetUpgrade).(string)\n\treturn v, ok\n}","tryCatchPattern":null,"preventionTips":["Set explicit defaults for every server.Key* option when constructing appOpts yourself","Keep custom testnet wrappers registering the full flag set","Mirror the tolerant cast.ToString pattern used for accounts-to-fund when forking","Cover programmatic testnet construction with a smoke test"],"tags":["cosmos-sdk","testnet","flags","panic","golang"],"backgroundTag":null,"analyzedSha":"031785bb6dc6b957c624e62bc64c184409c97d7b","analyzedAt":"2026-08-16T06:24:09.996Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}