{"record":{"id":"f887ad02750bfecb","repo":"chenhg5/cc-connect","slug":"resolve-cron-reply-target-w","errorCode":null,"errorMessage":"resolve cron reply target: %w","messagePattern":"resolve cron reply target: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/engine.go","lineNumber":1514,"sourceCode":"\t}\n\tif targetPlatform == nil {\n\t\treturn fmt.Errorf(\"platform %q not found for session %q\", platformName, sessionKey)\n\t}\n\n\trc, ok := targetPlatform.(ReplyContextReconstructor)\n\tif !ok {\n\t\treturn fmt.Errorf(\"platform %q does not support proactive messaging (cron)\", platformName)\n\t}\n\n\trunSessionKey := sessionKey\n\tvar replyCtx any\n\tvar err error\n\tif !job.Mute {\n\t\tif resolver, ok := targetPlatform.(CronReplyTargetResolver); ok {\n\t\t\tresolvedSessionKey, resolvedReplyCtx, err := resolver.ResolveCronReplyTarget(sessionKey, cronRunTitle(job))\n\t\t\tif err != nil {\n\t\t\t\tif !errors.Is(err, ErrNotSupported) {\n\t\t\t\t\treturn fmt.Errorf(\"resolve cron reply target: %w\", err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif resolvedSessionKey != \"\" {\n\t\t\t\t\trunSessionKey = resolvedSessionKey\n\t\t\t\t}\n\t\t\t\tif resolvedReplyCtx != nil {\n\t\t\t\t\treplyCtx = resolvedReplyCtx\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif replyCtx == nil {\n\t\treplyCtx, err = rc.ReconstructReplyCtx(runSessionKey)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"reconstruct reply context: %w\", err)\n\t\t}\n\t}\n","sourceCodeStart":1496,"sourceCodeEnd":1532,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/engine.go#L1496-L1532","documentation":"When a cron job is not muted, the engine optionally asks the platform to resolve a better reply target via the CronReplyTargetResolver interface (e.g. pick the right thread/topic for the run). If the resolver returns an error that is not ErrNotSupported (which would be tolerated and fall back to ReconstructReplyCtx), the engine aborts with 'resolve cron reply target: %w'. This means the platform tried to resolve the cron reply target and genuinely failed — not that it lacks support.","triggerScenarios":"targetPlatform implements CronReplyTargetResolver; resolver.ResolveCronReplyTarget(sessionKey, cronRunTitle(job)) returns a non-ErrNotSupported error — e.g. its internal API call to find the target thread/chat fails, the session key is malformed for that platform, or auth fails during resolution.","commonSituations":"Slack/Feishu-style adapters whose resolver must look up a thread via API and the API call 404s because the thread was deleted; session keys persisted before a workspace migration; bot token lacking scope to resolve the target channel.","solutions":["Unwrap the cause with errors.Is/As on the returned error to see the underlying platform failure; fix that first (auth, scope, deleted target).","Verify the cron job's session key still points at an existing chat/thread the bot can access.","Re-create the cron job against a currently valid session (send a message in the target chat and re-register the cron).","If the platform genuinely cannot resolve for this job type, it should return ErrNotSupported so the engine falls back to ReconstructReplyCtx — check the adapter is returning that sentinel correctly for unsupported cases.","Check platform token scopes for the channel/thread the resolver targets."],"exampleFix":"// before: resolver returns a hard error for a missing thread\nfunc (p *Platform) ResolveCronReplyTarget(key, title string) (string, any, error) {\n    th, err := p.findThread(key)\n    if err != nil { return \"\", nil, err } // aborts the whole cron run\n// after: signal not-supported so the engine falls back gracefully\n    th, err := p.findThread(key)\n    if err != nil {\n        return \"\", nil, fmt.Errorf(\"resolve thread: %w: %v\", core.ErrNotSupported, err)\n    }","handlingStrategy":"try-catch","validationCode":"// Go: pre-flight the resolver before registering the cron job\nif r, ok := platform.(core.CronReplyTargetResolver); ok {\n    if _, _, err := r.ResolveCronReplyTarget(sessionKey, \"preflight\"); err != nil && !errors.Is(err, core.ErrNotSupported) {\n        log.Printf(\"cron target unresolvable: %v\", err)\n    }\n}","typeGuard":"resolver, ok := targetPlatform.(core.CronReplyTargetResolver); if !ok { /* engine falls back to ReconstructReplyCtx */ }","tryCatchPattern":"if err := engine.ExecuteCronJob(job); err != nil {\n    if strings.Contains(err.Error(), \"resolve cron reply target\") {\n        slog.Error(\"cron reply target resolution failed; check thread/chat still exists and bot scopes\",\n            \"job\", job.ID, \"err\", err)\n    }\n}","preventionTips":["Re-create cron jobs after migrating workspaces so session keys stay valid.","Ensure the bot token has the scopes required to look up the target channel/thread.","In adapters, return core.ErrNotSupported (wrapped) for unresolvable-but-tolerable cases instead of hard errors.","Periodically validate that cron target threads/chats still exist; delete jobs pointing at deleted threads."],"tags":["cron","reply-target","resolver","wrapped-error"],"backgroundTag":"resource-not-found","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}