tinyhumansai/openhuman · error
agentWorkApi.control: ${args.action} requires a message
Error message
agentWorkApi.control: ${args.action} requires a message What it means
Guard inside agentWorkApi.control: the verbs 'continue' and 'follow_up' semantically require a user message (the run cannot be continued with nothing to say), so the client rejects them when message is missing or whitespace-only after trimming. The Rust handler enforces the same rule server-side; this is the early client check.
Source
Thrown at app/src/services/api/agentWorkApi.ts:110
params: limit === undefined ? {} : { limit },
});
log('list received groups=%d total=%d', response.groups.length, response.total);
return response;
},
/**
* Apply a control verb to one background agent run, returning the updated row.
*
* `continue` and `follow_up` carry the user's message; the client rejects an
* empty message for those verbs before hitting core (the Rust handler also
* enforces it). `stop` may carry an optional `reason`.
*/
control: async (args: AgentWorkControlArgs): Promise<AgentWorkRow> => {
const runId = args.runId?.trim();
if (!runId) throw new Error('agentWorkApi.control: runId is required');
const message = args.message?.trim();
if ((args.action === 'continue' || args.action === 'follow_up') && !message) {
throw new Error(`agentWorkApi.control: ${args.action} requires a message`);
}
const params: Record<string, unknown> = { runId, action: args.action };
if (message) params.message = message;
const reason = args.reason?.trim();
if (reason) params.reason = reason;
log('control runId=%s action=%s', runId, args.action);
const response = await callCoreRpc<AgentWorkControlResponse>({
method: 'openhuman.agent_work_control',
params,
});
log('control received status=%s', response.row.status);
return response.row;
},
};
View on GitHub (pinned to a221052e0d)
Solutions
- Validate non-empty trimmed input in the dialog before calling (and disable submit when empty)
- Trim the textarea value at the call boundary: const msg = input.trim(); if (!msg) return;
- For 'stop', keep using the optional reason field — no message needed there
Example fix
// before
await agentWorkApi.control({ runId, action: 'follow_up', message: draft }); // draft === ' '
// after
const msg = draft.trim();
if (!msg) return showError('Enter a message to continue this run');
await agentWorkApi.control({ runId, action: 'follow_up', message: msg }); Defensive patterns
Strategy: validation
Validate before calling
const message = args?.message?.trim() ?? '';
if (action === 'stop' || message) {
await agentWorkApi.control({ runId, action, ...(message ? { message } : {}) });
} Type guard
const isActionMessage = (a: string, m: unknown): boolean => a === 'continue' || a === 'follow_up' ? typeof m === 'string' && m.trim().length > 0 : true;
Try / catch
try { await agentWorkApi.control({ runId, action, message }); }
catch (e) { if (String(e.message).includes('requires a message')) showInputError('Enter a message'); else throw e; } Prevention
- Disable submit on empty textarea for continue/follow-up dialogs
- Trim input at the boundary and reject early with UI feedback
- Remember 'stop' needs no message — only reason (optional)
When it happens
Trigger: Calling control({ runId, action: 'continue' }) with no message, or with message: ' ' — e.g. a dialog whose textarea is submitted empty, or a send handler that forwards an unvalidated string.
Common situations: An 'Ask follow-up' modal whose submit button is not disabled on empty input; prefilled draft cleared by the user; message passed as an optional field and left undefined by a wrapper function.
Related errors
- agentWorkApi.control: runId is required
- agentTeamApi.get: teamId is required
- agentTeamApi.listMessages: teamId is required
- agentTeamApi.completeTask: teamId, taskId and memberId are r
- agentTeamApi.shutdownMember: teamId and memberId are require
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/99ba3173c566f58f.
Report an issue: GitHub.