nexu-io/open-design · error
connector refresh output must be a JSON object
Error message
connector refresh output must be a JSON object
What it means
The connector-tool refresh path in executeRefreshSource (refresh-service.ts:97-98) calls connectorService.execute and requires its returned `output` to be a plain JSON object: not null, not a primitive (string/number/boolean), and not an array. The live-artifact refresh merges this object into the document's dataJson via deepMergeBoundedJsonObject, which only works on a record shape. A non-object output means the connector tool's contract does not match what the refresh pipeline expects.
Source
Thrown at apps/daemon/src/live-artifacts/refresh-service.ts:98
projectId: string;
source: LiveArtifactSource;
signal: AbortSignal;
}): Promise<BoundedJsonObject> {
const { projectsRoot, projectId, source, signal } = options;
if (source.type === 'connector_tool') {
const connector = source.connector;
if (connector === undefined) throw new Error('connector refresh source requires connector metadata');
const result = await connectorService.execute(
{
connectorId: connector.connectorId,
toolName: connector.toolName,
input: source.input,
...(connector.accountLabel === undefined ? {} : { expectedAccountLabel: connector.accountLabel }),
},
{ projectsRoot, projectId, purpose: 'artifact_refresh', signal },
);
if (result.output === null || typeof result.output !== 'object' || Array.isArray(result.output)) {
throw new Error('connector refresh output must be a JSON object');
}
return result.output;
}
if (source.type !== 'daemon_tool' && source.type !== 'local_file') {
throw new Error(`refresh source ${source.type} is not supported yet`);
}
return executeLocalDaemonRefreshSource({ projectsRoot, projectId, source, signal });
}
export async function refreshLiveArtifact(options: RefreshLiveArtifactOptions): Promise<RefreshLiveArtifactResult> {
return withLiveArtifactRefreshLock(options, async (lock) => {
const refreshId = lock.metadata.refreshId;
let sequence = 0;
const appendLog = async (entry: {
step: string;
status: 'running' | 'succeeded' | 'failed' | 'cancelled' | 'skipped';
startedAt: Date;View on GitHub (pinned to 5be4028344)
Solutions
- Make the connector tool return a top-level JSON object (wrap scalars/arrays, e.g. { items: [...] } or { value: 42 }).
- If the connector output cannot change, add an outputMapping.dataPaths on the source so the refresh reshapes the array/scalar into an object before merge.
- Verify the connector implementation's execute() resolves with { output: { ... } } and not { output: null }.
Example fix
// before: connector returns a bare array / scalar
const result = await connectorService.execute(req, ctx);
// result.output === ['a', 'b'] -> throws 'connector refresh output must be a JSON object'
// after: connector returns an object wrapping the payload
// result.output === { items: ['a', 'b'] } Defensive patterns
Strategy: type-guard
Validate before calling
// Validate connector output shape before relying on it in a refresh flow.
function isPlainJsonObject(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
const result = await connectorService.execute(req, ctx);
if (!isPlainJsonObject(result.output)) {
throw new TypeError('expected connector output to be a JSON object');
} Type guard
function isConnectorJsonObject(output: unknown): output is Record<string, unknown> {
return output !== null && typeof output === 'object' && !Array.isArray(output);
} Try / catch
try {
await refreshLiveArtifact(opts);
} catch (err) {
if (err instanceof Error && err.message === 'connector refresh output must be a JSON object') {
// surface a user-facing 'connector returned an unexpected shape' message
}
throw err;
} Prevention
- Define the connector tool's output as an object schema and assert it in tests.
- Wrap any scalar/array connector payload in an object before returning from execute().
- Never let a connector resolve with output: null on empty results; use {} or { items: [] }.
When it happens
Trigger: A live artifact whose document.sourceJson.type === 'connector_tool' is refreshed, connectorService.execute succeeds, but result.output is null, a string/number/boolean, or a JSON array (Array.isArray === true).
Common situations: A connector tool returns a raw scalar (e.g. a single count or status string) instead of an object; a connector returns a top-level array of records; the connector service yields null on an empty result set; a custom connector implementation forgot to wrap its payload in an object.
Related errors
- connector refresh source requires connector metadata
- refresh source ${source.type} is not supported yet
- No refresh source is available yet.
- Refresh is disabled for this artifact source.
- ${firstIssue.path}: ${firstIssue.message}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/98dfe45b33820fa7.
Report an issue: GitHub.