paperclipai/paperclip · error · RunnerGoalActionError

session_unavailable

session_unavailable

Error message

session_unavailable: Agent task session is unavailable.

What it means

Inside the transactional block of `act`, the code selects an `agentTaskSessions` row keyed by (companyId, agentId, adapterType, taskKey=issueId) FOR UPDATE. If no session row exists it throws `session_unavailable`. A goal action requires a live agent task session to attach to; goals cannot exist sessionless.

Source

Thrown at server/src/services/runner-goals.ts:385

        initialProjection.capability.reasonCode ?? "session_goals_unsupported",
        initialProjection.capability.reason ?? "Session goals are unsupported.",
      );
    }

    const accepted = await db.transaction(async (tx) => {
      await tx.insert(agentTaskSessions).values({
        companyId,
        agentId: request.agentId,
        adapterType: binding.agent!.adapterType,
        taskKey: issueId,
      }).onConflictDoNothing();
      const [session] = await tx.select().from(agentTaskSessions).where(and(
        eq(agentTaskSessions.companyId, companyId),
        eq(agentTaskSessions.agentId, request.agentId),
        eq(agentTaskSessions.adapterType, binding.agent!.adapterType),
        eq(agentTaskSessions.taskKey, issueId),
      )).limit(1).for("update");
      if (!session) throw new RunnerGoalActionError("session_unavailable", "Agent task session is unavailable.");

      const currentGoal = storedGoal(session);
      const currentProjection: RunnerGoalProjection = {
        ...initialProjection,
        sessionId: session.id,
        capability: storedCapability(session, fallbackCapability),
        goal: currentGoal,
        workingNow: currentGoal?.workingNow ?? false,
        revision: session.goalRevision,
        observedAt: session.goalObservedAt?.toISOString() ?? null,
      };

      const [existingAction] = await tx.select().from(agentSessionGoalActions).where(and(
        eq(agentSessionGoalActions.sessionId, session.id),
        eq(agentSessionGoalActions.requestId, request.requestId),
      )).limit(1);
      if (existingAction) {
        if (!isSameGoalActionRequest(existingAction.payloadJson, request)) {

View on GitHub (pinned to 01ad858492)

Solutions

  1. Start an agent task session for the issue first (let the agent check out/start work), then send goal actions
  2. Re-check session existence via the sessions API before calling `act`
  3. If the session died, restart the agent run and re-establish the session before managing goals
  4. Verify the agentId and adapterType in your request match the session that was actually created

Example fix

// before
await runnerGoals.act(companyId, issueId, { agentId, action: 'pause' }); // session may not exist
// after
const sessions = await listAgentTaskSessions(companyId, agentId, issueId);
if (sessions.length === 0) await startAgentTask(companyId, issueId, agentId);
await runnerGoals.act(companyId, issueId, { agentId, action: 'pause' });
Defensive patterns

Strategy: validation

Validate before calling

const sessions = await listAgentTaskSessions(companyId, { agentId, taskKey: issueId });
if (sessions.length === 0) throw new Error('no agent task session for this issue');

Type guard

function hasSession(sessions) {
  return Array.isArray(sessions) && sessions.length > 0;
}

Try / catch

try {
  await runnerGoals.act(companyId, issueId, req);
} catch (e) {
  if (e instanceof RunnerGoalActionError && e.code === 'session_unavailable') {
    await startAgentTask(companyId, issueId, req.agentId); // then retry
  } else throw e;
}

Prevention

When it happens

Trigger: Calling a goal action when no agent task session has been started for that issue/agent; the session was terminated or cleaned up before the action; adapterType mismatch between the stored agent and session rows so the WHERE clause misses; issueId not matching session.taskKey.

Common situations: Runner retries goal actions after session teardown; agent crashed and its session was reaped; calling goals before the agent has started working on the issue (session not yet created); reassignment changed adapterType.

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 paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/6c1b0ef9e2734bb0. Report an issue: GitHub.