{"record":{"id":"40dec38a543d5120","repo":"gastownhall/beads","slug":"wisp-s-not-found","errorCode":null,"errorMessage":"wisp %s not found","messagePattern":"wisp (.+?) not found","errorType":"error_code","errorClass":null,"httpStatus":404,"severity":"error","filePath":"internal/storage/issueops/promote.go","lineNumber":14,"sourceCode":"package issueops\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/steveyegge/beads/internal/storage\"\n\t\"github.com/steveyegge/beads/internal/types\"\n)\n\n//nolint:gosec // G201: table names are hardcoded constants\nfunc PromoteFromEphemeralInTx(ctx context.Context, tx DBTX, id string, actor string) error {\n\tif !IsActiveWispInTx(ctx, tx, id) {\n\t\treturn fmt.Errorf(\"wisp %s not found\", id)\n\t}\n\n\tissue, err := GetIssueInTx(ctx, tx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get wisp for promote: %w\", err)\n\t}\n\tif issue == nil {\n\t\treturn fmt.Errorf(\"wisp %s not found\", id)\n\t}\n\n\t// A promoted wisp is fully durable: clear BOTH wisp-plane flags, not just\n\t// Ephemeral. A no-history wisp (Ephemeral=false, NoHistory=true) promoted\n\t// with NoHistory intact lands in the issues table still flag-marked as\n\t// wisp-plane state, and everything that infers the plane from flags —\n\t// most damagingly import's table routing — silently re-planes it back\n\t// into the (default-export-excluded) wisps table, dropping its relations\n\t// on the way (bd-r9uce). Post-promotion the flag has no meaning.\n\tissue.Ephemeral = false","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/promote.go#L1-L32","documentation":"PromoteFromEphemeralInTx first checks IsActiveWispInTx; if the ID is not currently an active wisp, it fails immediately with 'wisp %s not found'. Promotion only applies to live ephemeral (wisp-plane) issues, so any ID that is absent, already promoted, or fully deleted produces this error.","triggerScenarios":"Calling PromoteFromEphemeralInTx with an ID that is not an active wisp: the issue does not exist, was already promoted to the durable plane, was deleted, or exists only as a durable (non-ephemeral) issue.","commonSituations":"Double-promotion after a retry that actually succeeded the first time; promoting an ID copied from the issues table instead of the wisp table; a stale script referencing wisps that were garbage-collected.","solutions":["Check the issue's current plane/state before promoting (query the wisp table or use a read API) and skip already-promoted IDs","Treat this as idempotent success if the ID now exists as a durable issue — the promote already happened","Re-check the ID for typos or stale references; run bd list to find current wisp IDs","If the wisp was created in another clone, sync first (bd dolt pull)"],"exampleFix":"// before\nif err := issueops.PromoteFromEphemeralInTx(ctx, tx, id, actor); err != nil {\n\treturn err\n}\n// after\nif err := issueops.PromoteFromEphemeralInTx(ctx, tx, id, actor); err != nil {\n\tif strings.Contains(err.Error(), \"not found\") && issueExistsDurable(ctx, tx, id) {\n\t\treturn nil // already promoted; idempotent retry\n\t}\n\treturn err\n}","handlingStrategy":"validation","validationCode":"active, err := isActiveWisp(ctx, tx, id) // check the wisp plane first\nif err != nil { return err }\nif !active { return fmt.Errorf(\"skip %s: not an active wisp\", id) }","typeGuard":"func isActiveWisp(ctx context.Context, tx issueops.DBTX, id string) (bool, error)\n// plus: func isDurableIssue(ctx context.Context, tx issueops.DBTX, id string) (bool, error)\n// to distinguish 'already promoted' from 'never existed'","tryCatchPattern":"if err := issueops.PromoteFromEphemeralInTx(ctx, tx, id, actor); err != nil {\n\tif isWispNotFound(err) {\n\t\tif exists, _ := isDurableIssue(ctx, tx, id); exists {\n\t\t\treturn nil // idempotent: already promoted\n\t\t}\n\t\treturn fmt.Errorf(\"wisp %s does not exist\", id)\n\t}\n\treturn err\n}","preventionTips":["Make promote idempotent: treat already-durable as success","Fetch wisp IDs from a live query, not cached lists","Sync before batch promotions if wisps may originate in other clones","Verify IDs against bd list output before scripting promotions"],"tags":["go","storage","wisp","promote","not-found"],"backgroundTag":"wisp-not-found","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}