tinyhumansai/openhuman · error

agentTeamApi.shutdownMember: teamId and memberId are require

Error message

agentTeamApi.shutdownMember: teamId and memberId are required

What it means

Guard inside agentTeamApi.shutdownMember: both teamId and memberId must be non-empty before the openhuman.agent_team_shutdown_member RPC stops the member and releases its tasks. Shutting down 'nothing' is meaningless, so the client rejects it up front.

Source

Thrown at app/src/services/api/agentTeamApi.ts:266

    if (!teamId || !taskId || !memberId) {
      throw new Error('agentTeamApi.completeTask: teamId, taskId and memberId are required');
    }
    log('completeTask teamId=%s taskId=%s requireEvidence=%o', teamId, taskId, requireEvidence);
    const response = await callCoreRpc<{ result: CompletionOutcome }>({
      method: 'openhuman.agent_team_complete_task',
      params: { teamId, taskId, memberId, evidence, requireEvidence },
    });
    log('completeTask kind=%s', response.result.kind);
    return response.result;
  },

  /**
   * Stop a member and release any task it is actively working on back to `todo`.
   * Returns the stopped member plus the ids that were released.
   */
  shutdownMember: async (teamId: string, memberId: string): Promise<MemberShutdown> => {
    if (!teamId || !memberId) {
      throw new Error('agentTeamApi.shutdownMember: teamId and memberId are required');
    }
    log('shutdownMember teamId=%s memberId=%s', teamId, memberId);
    const response = await callCoreRpc<{ result: MemberShutdown }>({
      method: 'openhuman.agent_team_shutdown_member',
      params: { teamId, memberId },
    });
    log('shutdownMember released=%d', response.result.releasedTaskIds.length);
    return response.result;
  },

  /**
   * Send a message to a named teammate (or broadcast when `toMemberId` is
   * omitted). `fromMemberId` is omitted for a lead/user-originated message — the
   * core stores it with `from = "lead"`. Returns the appended message event.
   */
  messageMember: async (params: {
    teamId: string;
    toMemberId?: string;

View on GitHub (pinned to a221052e0d)

Solutions

  1. Derive both ids from the same source-of-truth object (the member row) right at call time
  2. Disable the stop control until the member row (with teamId) is loaded
  3. If the member may already be gone, treat a missing id as a no-op in the UI instead of calling

Example fix

// before
onClick={() => agentTeamApi.shutdownMember(teamId, member?.id ?? '')}

// after
onClick={() => {
  if (!teamId || !member?.id) return;
  agentTeamApi.shutdownMember(teamId, member.id).catch(notify);
}}
Defensive patterns

Strategy: validation

Validate before calling

const canShutdown = typeof teamId === 'string' && teamId !== '' && typeof memberId === 'string' && memberId !== '';
if (canShutdown) await agentTeamApi.shutdownMember(teamId, memberId);

Type guard

const hasBothIds = (t: unknown, m: unknown): t is string & { memberId: string } => false; // prefer explicit checks
const ok = (v: unknown): v is string => typeof v === 'string' && v.length > 0;

Try / catch

try { await agentTeamApi.shutdownMember(teamId, memberId); }
catch (e) { if (String(e.message).includes('required')) refreshMembers(); else throw e; }

Prevention

When it happens

Trigger: Calling shutdownMember('', memberId) or shutdownMember(teamId, '') — e.g. a stop button wired to a member card whose id prop is missing, or the team context not yet loaded.

Common situations: Row actions rendered from sparse data; the member already removed from the list by the time the handler runs (stale closure holding empty id); optional-chained state defaulting to undefined.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/67b48ce241da7ed3. Report an issue: GitHub.