LemmyNet/lemmy · error

err getting actor {actor_type:?} {actor_apub_id}: {e:?}

Error message

err getting actor {actor_type:?} {actor_apub_id}: {e:?}

What it means

get_actor_cached wraps any failure while loading an actor (person/community/etc.) from the DB by its ActivityPub ID into this anyhow error. It uses the underlying LemmyError as context, so the root cause is in the debug-printed inner error. send_retry_loop calls it before serializing the activity.

Source

Thrown at crates/apub/send/src/util.rs:174

            .into(),
        )),
        ActorType::Person => Right(Left(
          Person::read_from_apub_id(pool, &url)
            .await?
            .context("apub person not found")?
            .into(),
        )),
        ActorType::MultiCommunity => Left(Right(
          MultiCommunity::read_from_apub_id(pool, &url)
            .await?
            .context("apub multi-comm not found")?
            .into(),
        )),
      };
      Result::<_, LemmyError>::Ok(Arc::new(actor))
    })
    .await
    .map_err(|e| anyhow::anyhow!("err getting actor {actor_type:?} {actor_apub_id}: {e:?}"))
}

type CachedActivityInfo = Option<Arc<SentActivity>>;
/// activities are immutable so cache does not need to have TTL
/// May return None if the corresponding id does not exist or is a received activity.
/// Holes in serials are expected behaviour in postgresql
/// todo: cache size should probably be configurable / dependent on desired memory usage
pub(crate) async fn get_activity_cached(
  pool: &mut DbPool<'_>,
  activity_id: ActivityId,
) -> Result<CachedActivityInfo> {
  static ACTIVITIES: LazyLock<Cache<ActivityId, CachedActivityInfo>> =
    LazyLock::new(|| Cache::builder().max_capacity(10000).build());
  ACTIVITIES
    .try_get_with(activity_id, async {
      Ok(Some(Arc::new(SentActivity::read(pool, activity_id).await?)))
    })
    .await

View on GitHub (pinned to 439734dd63)

Solutions

  1. Check the inner debug error to see whether it is not-found vs a DB failure
  2. Verify the actor still exists and is not marked deleted/removed in the database
  3. Confirm activity.actor_type matches the actual actor kind at actor_apub_id
  4. If DB-related, check database connectivity/health and retry the federation

Example fix

// before
let actor = get_actor_cached(pool, activity.actor_type, &actor_apub_id).await?; // fails for removed actor
// after
let actor = match get_actor_cached(pool, activity.actor_type, &actor_apub_id).await {
  Ok(a) => a,
  Err(e) => { warn!("actor gone, dropping activity: {e:?}"); continue; } // skip rather than retry forever
};
Defensive patterns

Strategy: try-catch

Validate before calling

// before sending, check the actor row exists
// SELECT 1 FROM actor WHERE ap_id = $1 AND deleted = false AND removed = false

Try / catch

match get_actor_cached(pool, activity.actor_type, &ap_id).await {
  Ok(actor) => send_with(actor),
  Err(e) => warn!("actor unavailable, dropping activity: {e:?}"),
}

Prevention

When it happens

Trigger: Federation send for an activity whose actor_type + actor_apub_id does not resolve to a live actor row: deleted/removed actor, actor purged from DB, wrong actor_type, or an underlying DB query failure.

Common situations: Trying to federate an activity authored by a user or community that has since been deleted or removed by moderators; DB connectivity issues on the instance; stale cache entries pointing at removed actors.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of LemmyNet/lemmy@439734dd63 (2026-09-06). Data as JSON: /api/errors/3de512c9f4433841. Report an issue: GitHub.