The scheduleChildTask method schedules a one-off child task from inside a running taskRunner
invocation. The platform delivers each child back to the same taskRunner as a separate
invocation, allowing you to break large work units into smaller pieces that run independently
and in parallel.
Use this method to walk a tree, drain a paginated list, or split a batch into per-chunk invocations. The child runs once when the platform picks it up; it does not recur.
1 2scheduleChildTask(request: ChildTaskScheduleRequest): Promise<ChildTaskScheduleResponse>
ChildTaskScheduleRequest1 2{ scanId: string; // From the current TaskRunnerPayload taskExecutionId: string; // From the current TaskRunnerPayload task: ChildTask; // The child task to schedule connectionId: string; // From the current TaskRunnerPayload }
ChildTask1 2{ parentTaskId: string; // The taskId of the invocation calling scheduleChildTask taskType: ForgeTaskType; // One of types.FORGE_TASK_TYPES (closed enum) taskId: string; // Deterministic UUID for this child temporalTaskMetadata?: any; // Optional, platform-only payload persistentTaskMetadata?: any; // Optional, platform-only payload }
The scanId, taskExecutionId, and connectionId fields are taken from the TaskRunnerPayload
your handler received. They thread the child into the same scan as the parent so the platform
can correlate the fan-out tree.
Child tasks typically share their parent's taskType. The same taskRunner routine handles
both the root tick and the per-item children, with TaskInfo.metadata carrying the
per-invocation context (for example, the specific folder being walked). See
Task types.
1 2import { graph, types } from '@forge/teamwork-graph'; import { v5 as uuidv5 } from 'uuid'; import { kvs } from '@forge/kvs'; const TASK_NAMESPACE = uuidv5('my-connector', uuidv5.URL); interface FolderMetadata { folderId: string; pageToken?: string; } async function scheduleFolderDiscovery( request: TaskRunnerPayload, meta: FolderMetadata, ): Promise<void> { // Deterministic id keyed by (scanId, parent, work unit). Re-deliveries of // the parent produce the same childTaskId so every duplicate runner invocation // lands on the same TaskInfo row and short-circuits via completed: true. const childKey = `folder:${meta.folderId}:page:${meta.pageToken ?? '0'}`; const childTaskId = uuidv5( `${request.scanId}:${request.taskId}:${childKey}`, TASK_NAMESPACE, ); // Persist the per-invocation context the child will need. Read it back // inside taskRunner using the child's taskId. await kvs.set(`taskInfo:${childTaskId}`, { taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_INCREMENTAL, connectionId: request.connectionId, metadata: meta, }); const response = await graph.scheduleChildTask({ scanId: request.scanId, taskExecutionId: request.taskExecutionId, connectionId: request.connectionId, task: { parentTaskId: request.taskId, taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_INCREMENTAL, taskId: childTaskId, }, }); if (response.error) { console.error('Failed to schedule child task:', response.error); } }
1 2async function executeFolderDiscovery( request: TaskRunnerPayload, meta: FolderMetadata, ): Promise<void> { const page = await listFolderPage(meta.folderId, { pageToken: meta.pageToken }); // Schedule a child per subfolder for (const sub of page.subfolders) { await scheduleFolderDiscovery(request, { folderId: sub.id }); } // Continue pagination by self-scheduling for the next page if (page.nextPageToken) { await scheduleFolderDiscovery(request, { folderId: meta.folderId, pageToken: page.nextPageToken, }); } }
Derive each child's taskId deterministically (for example with
uuidv5(scanId + parentTaskId + childKey)) so re-deliveries of the parent reuse the same child
taskId. The platform creates a fresh task execution for every scheduleChildTask call
regardless of the taskId you pass; deterministic IDs are how your taskRunner recognizes a
redelivery and short-circuits via the TaskInfo.completed check. Random UUIDs make every
retried child look like new work to your runner, so the full pipeline (third-party API calls,
setObjects, etc.) runs again on every retry. See
At-least-once delivery and idempotency.
temporalTaskMetadata and persistentTaskMetadata are platform-only; their propagation back to
taskRunner is not a stable contract today. Use KVS keyed by the child taskId for any
per-invocation context your handler needs.
The method validates the following:
connectionId: Required, must be a non-empty string.task.parentTaskId: Required, must be a non-empty string. Pass the taskId of the
invocation calling scheduleChildTask.task.taskType: Required, non-empty, max 255 characters, and must be one of the values
exposed by types.FORGE_TASK_TYPES.task.taskId: Required, must be a valid UUID. Derive deterministically to make the call
idempotent under at-least-once delivery.The scanId and taskExecutionId fields are not validated by the SDK client-side; they are
validated server-side. Pass them through unchanged from the current TaskRunnerPayload.
| Error message | Description |
|---|---|
connectionId is required | The connectionId is missing or empty. |
task.parentTaskId is required | The task.parentTaskId field is missing. Pass request.taskId from the current TaskRunnerPayload. |
task.taskType is required | The task.taskType is missing or empty. |
task.taskType must not exceed 255 characters | The task.taskType is longer than the 255-character limit. |
task.taskType must be one of: user_ingestion_full, user_ingestion_incremental, ... | The task.taskType is not one of the values exposed by types.FORGE_TASK_TYPES. |
task.taskId must be a valid UUID format | The provided child taskId is not a valid UUID. |
Failed to schedule child tasks: Not Found - Scan not found | Server-side: the scanId does not match an active scan on the connection. Common when reusing a stale scanId outside of an active taskRunner invocation. |
The method returns a promise that resolves to a ChildTaskScheduleResponse.
1 2{ status: string; // Status string from the platform (e.g. "ACCEPTED") message: string; // Human-readable status message error?: string; // Error message if the request failed originalError?: unknown; // Original platform error object, when available }
On success, the response carries status and message. On failure, the SDK returns an object
with error and originalError populated. Branch on response.error to detect failures. The
child taskId you submitted is yours to track in KVS; the response does not echo it.
The SDK provides type-safe request and response objects that ensure compile-time validation:
1 2import { graph, types } from '@forge/teamwork-graph'; const request: types.ChildTaskScheduleRequest = { scanId: incomingPayload.scanId, taskExecutionId: incomingPayload.taskExecutionId, connectionId: incomingPayload.connectionId, task: { parentTaskId: incomingPayload.taskId, taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_INCREMENTAL, taskId: 'a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d', }, }; const response: types.ChildTaskScheduleResponse = await graph.scheduleChildTask(request); if (response.error) { console.error('Failed to schedule child task:', response.error); }
The type system ensures:
task.taskType is constrained to the ForgeTaskType union exposed by types.FORGE_TASK_TYPES.request has the correct ChildTaskScheduleRequest structure with the scan and execution
context wired in.response is properly typed as ChildTaskScheduleResponse with optional error and
originalError.Rate this page: