{"record":{"id":"0f68593cbf7fe2a8","repo":"larksuite/cli","slug":"expected-version-s-got-q","errorCode":null,"errorMessage":"expected version %s, got %q","messagePattern":"expected version (.+?), got %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/selfupdate/updater.go","lineNumber":461,"sourceCode":"\t\t}\n\t}\n\tctx, cancel := context.WithTimeout(context.Background(), verifyTimeout)\n\tdefer cancel()\n\tout, err := exec.CommandContext(ctx, exe, \"--version\").Output()\n\tif ctx.Err() == context.DeadlineExceeded {\n\t\treturn fmt.Errorf(\"binary verification timed out after %s\", verifyTimeout)\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"binary not executable: %w\", err)\n\t}\n\tfields := strings.Fields(strings.TrimSpace(string(out)))\n\tif len(fields) == 0 {\n\t\treturn fmt.Errorf(\"empty version output\")\n\t}\n\tactual := strings.TrimPrefix(fields[len(fields)-1], \"v\")\n\texpected := strings.TrimPrefix(expectedVersion, \"v\")\n\tif actual != expected {\n\t\treturn fmt.Errorf(\"expected version %s, got %q\", expectedVersion, actual)\n\t}\n\treturn nil\n}\n\n// Truncate returns the last maxLen runes of s.\nfunc Truncate(s string, maxLen int) string {\n\tif maxLen <= 0 {\n\t\treturn \"\"\n\t}\n\tr := []rune(s)\n\tif len(r) <= maxLen {\n\t\treturn s\n\t}\n\treturn string(r[len(r)-maxLen:])\n}\n\n// resolveExe returns the resolved path of the current running binary.\nfunc (u *Updater) resolveExe() (string, error) {","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/selfupdate/updater.go#L443-L479","documentation":"VerifyBinary compares the actual version reported by the new binary (last whitespace-separated field, with a leading \"v\" trimmed) against the expected version it downloaded, and throws \"expected version %s, got %q\" on mismatch. This prevents installing a binary that is not the release the updater intended, e.g. a stale or mislabeled artifact.","triggerScenarios":"The binary runs and prints a version, but after TrimPrefix(\"v\") the last field of --version output does not equal the expectedVersion string passed to VerifyBinary.","commonSituations":"Release tag updated but the asset still points at an old build; CDN/cache serving a stale artifact; version string format changed (e.g. \"1.2.3 (abc123)\" where the last field is a commit hash, not the version); expected version passed with inconsistent \"v\" prefix handling.","solutions":["Compare the actual --version output with the release tag; re-download or re-publish the correct asset","Purge CDN/proxy caches serving the old artifact","Make the binary's last --version field exactly the version (optionally \"v\"-prefixed); fix the version stamping in the build","Ensure the updater passes the same expectedVersion string form that the binary prints"],"exampleFix":"// before (binary prints build info last)\nfmt.Printf(\"v%s (%s)\\n\", version, commit) // last field is commit -> mismatch\n// after\nfmt.Printf(\"v%s\\n\", version) // last field is the version","handlingStrategy":"validation","validationCode":"out, _ := exec.Command(binPath, \"--version\").Output()\nfields := strings.Fields(strings.TrimSpace(string(out)))\nif len(fields) == 0 { return fmt.Errorf(\"no version output\") }\nactual := strings.TrimPrefix(fields[len(fields)-1], \"v\")\nexpected := strings.TrimPrefix(expectedVersion, \"v\")\nif actual != expected { return fmt.Errorf(\"stale artifact: want %s got %s\", expected, actual) }","typeGuard":null,"tryCatchPattern":"if err := update.VerifyBinary(ctx, path, expectedVersion); err != nil {\n    var mismatch = err.Error()\n    if strings.HasPrefix(mismatch, \"expected version\") {\n        // purge cache and retry download once with the pinned asset URL\n        return retryWithFreshDownload()\n    }\n    return err\n}","preventionTips":["Print only the version token (optionally v-prefixed) as the last --version field","Pin release asset URLs and bust CDN caches on re-publish","Stamp version at build time via -ldflags and assert it in release CI","Keep expectedVersion format consistent with the binary's output format"],"tags":["selfupdate","version-mismatch","release"],"backgroundTag":"version-mismatch","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}