charmbracelet/crush · error

cannot continue a child session

Error message

cannot continue a child session: %s

What it means

After a session is fetched successfully, resolveSession rejects it if ParentSessionID is non-empty. Child sessions are created for sub-agents/tasks spawned from a parent run and are not meant to be resumed directly, so continuing one is blocked with this error.

Solutions

  1. Find the parent session ID (the child's ParentSessionID field) and pass that to --continue instead.
  2. List sessions and choose one without a parent (a top-level session).
  3. If you need the child's context, resume the parent session which contains the child's history.

Example fix

// before
crush run --continue <child-session-id> "continue"
// after
# resume the parent of that child session instead
crush run --continue <parent-session-id> "continue"
Defensive patterns

Strategy: validation

Validate before calling

sess, err := c.GetSession(ctx, wsID, contID)
if err == nil && sess.ParentSessionID != "" {
    return fmt.Errorf("refusing to continue child session %s; use parent %s", contID, sess.ParentSessionID)
}

Type guard

func isTopLevel(s proto.Session) bool { return s.ParentSessionID == "" }

Try / catch

sess, err := resolveSession(ctx, c, wsID, contID, false)
if err != nil && strings.HasPrefix(err.Error(), "cannot continue a child session:") {
    // resolve the parent and retry
    parentID := childToParent[contID]
    sess, err = resolveSession(ctx, c, wsID, parentID, false)
}

Prevention

When it happens

Trigger: Calling `crush run --continue <childSessionID>` where the session record has a non-empty ParentSessionID, i.e. the ID belongs to a task/sub-agent session rather than a top-level session.

Common situations: Copying a session ID from a child task in the session list or logs instead of the parent; picking the wrong row when several sessions share a similar name; scripting against session IDs harvested from internal message records.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/439069802ee62348. Report an issue: GitHub.

Appendix: source

Thrown at internal/cmd/run.go:680

			label, modelID, strings.Join(names, ", "),
		)
	}
	return matches[0], nil
}

// resolveSession returns the session to use for a non-interactive run.
// If continueSessionID is set it fetches that session; if useLast is set it
// returns the most recently updated top-level session; otherwise it creates a
// new one.
func resolveSession(ctx context.Context, c *client.Client, wsID, continueSessionID string, useLast bool) (*proto.Session, error) {
	switch {
	case continueSessionID != "":
		sess, err := c.GetSession(ctx, wsID, continueSessionID)
		if err != nil {
			return nil, fmt.Errorf("session not found: %s", continueSessionID)
		}
		if sess.ParentSessionID != "" {
			return nil, fmt.Errorf("cannot continue a child session: %s", continueSessionID)
		}
		return sess, nil

	case useLast:
		sessions, err := c.ListSessions(ctx, wsID)
		if err != nil || len(sessions) == 0 {
			return nil, fmt.Errorf("no sessions found to continue")
		}
		last := sessions[0]
		for _, s := range sessions[1:] {
			if s.UpdatedAt > last.UpdatedAt && s.ParentSessionID == "" {
				last = s
			}
		}
		return &last, nil

	default:
		return c.CreateSession(ctx, wsID, "non-interactive")

View on GitHub (pinned to 7944b8e522)