chenhg5/cc-connect · error

resolve parent channel %s: %w

Error message

resolve parent channel %s: %w

What it means

When the cron target channel is itself a thread, resolveCronReplyTarget must resolve the parent channel (ch.ParentID) to know what kind of standalone thread to create. Failure to resolve the parent is wrapped as "resolve parent channel <id>: <cause>". An empty ParentID returns core.ErrNotSupported separately.

Source

Thrown at platform/discord/discord.go:442

func resolveCronReplyTarget(sessionKey, title string, ops discordThreadOps) (string, replyContext, error) {
	channelID, err := parseDiscordSessionKeyChannelID(sessionKey)
	if err != nil {
		return "", replyContext{}, err
	}

	ch, err := ops.ResolveChannel(channelID)
	if err != nil {
		return "", replyContext{}, fmt.Errorf("resolve channel %s: %w", channelID, err)
	}
	parentChannelID := channelID
	parentType := ch.Type
	if isThreadChannelType(ch.Type) {
		if ch.ParentID == "" {
			return "", replyContext{}, core.ErrNotSupported
		}
		parent, err := ops.ResolveChannel(ch.ParentID)
		if err != nil {
			return "", replyContext{}, fmt.Errorf("resolve parent channel %s: %w", ch.ParentID, err)
		}
		parentChannelID = ch.ParentID
		parentType = parent.Type
	}

	threadType, ok := standaloneThreadType(parentType)
	if !ok {
		return "", replyContext{}, core.ErrNotSupported
	}

	thread, err := ops.StartStandaloneThread(parentChannelID, freshThreadName(title), threadType, 1440)
	if err != nil {
		return "", replyContext{}, fmt.Errorf("start thread in channel %s: %w", parentChannelID, err)
	}
	if err := ops.JoinThread(thread.ID); err != nil {
		slog.Debug("discord: join fresh thread failed", "thread", thread.ID, "error", err)
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the wrapped cause: not-found usually means the parent channel was deleted.
  2. Re-point the cron job at a normal text channel instead of a thread of a deleted channel.
  3. Grant the bot ViewChannel on the parent channel/category, not just the thread.
  4. Retry on transient API errors with backoff.

Example fix

// before
parent, err := ops.ResolveChannel(ch.ParentID)
if err != nil {
    return "", replyContext{}, fmt.Errorf("resolve parent channel %s: %w", ch.ParentID, err)
}
// after
parent, err := ops.ResolveChannel(ch.ParentID)
if err != nil {
    slog.Warn("discord: cannot resolve thread parent for cron, using thread channel directly", "parent", ch.ParentID, "error", err)
    parentChannelID = channelID
    parentType = ch.Type
}
Defensive patterns

Strategy: fallback

Validate before calling

if ch.ParentID == "" {
    return core.ErrNotSupported // check before resolving parent
}
if _, err := ops.ResolveChannel(ch.ParentID); err != nil {
    slog.Warn("discord: thread parent unresolvable for cron", "parent", ch.ParentID)
}

Try / catch

parent, err := ops.ResolveChannel(ch.ParentID)
if err != nil {
    // degrade: create the standalone thread in the thread's own channel scope or skip
    return "", replyContext{}, fmt.Errorf("resolve parent channel %s: %w", ch.ParentID, err)
}

Prevention

When it happens

Trigger: ops.ResolveChannel(ch.ParentID) fails for the thread's parent — parent channel deleted, bot lacks access to the parent (but can see the thread), or a transient Discord API error.

Common situations: Threads whose parent channel was deleted (orphaned threads), permission asymmetry where the bot sees a thread but not its parent category/channel, Discord API hiccups during cron firing.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/9548e3cac7f60211. Report an issue: GitHub.