ctx.http now supports simple cookie-backed auth flows automatically.
Jobs can declare shared base URLs and default headers with the top-level http block,
and handlers can still derive explicit child clients via ctx.http.withDefaults(...)
when they need narrower defaults inside a run.
Jobs can also bind env-backed credential profiles so auth actions do not need plaintext usernames/passwords in job payloads.
For local development, those values are commonly loaded through direnv. Modules should
still think in terms of explicit job http and credentials, not ambient env access.
- Cookies are captured from
Set-Cookieresponse headers. - Matching cookies are sent on later requests in the same job run.
- Cookie state is scoped to the current run only.
- Cookies are not persisted across runs.
- Modules should not store session cookies in
memory.
This means an auth module can be simple:
admin.loginreceives a resolved credential object fromctx.credential.- The server returns session cookies.
- Later actions such as
admin.meoradmin.create-userreuse that session automatically.
No module-level cookie jar or manual cookie plumbing is needed.
Example:
{
"credentials": {
"adminQa": {
"fromEnv": {
"username": "DISPATCH_ADMIN_USERNAME",
"password": "DISPATCH_ADMIN_PASSWORD"
}
}
},
"scenario": {
"steps": [
{
"id": "login",
"action": "admin.login",
"credential": "adminQa",
"payload": {}
}
]
}
}And the action contract:
defineAction({
description: 'Log in to the admin surface.',
schema: z.object({}),
credentialSchema: z.object({
username: z.string(),
password: z.string(),
}),
handler: async (ctx) => {
const credential = ctx.credential;
// ...
},
});For stable request config that is shared across the whole run, declare it in the job:
{
"http": {
"baseUrl": "https://api.example.com",
"defaultHeaders": {
"x-client": "dispatch"
}
}
}Jobs may load those job-level HTTP values through ${env.*} so the workflow stays portable
while repo-local setup stays outside git:
{
"http": {
"baseUrl": "${env.DISPATCH_HTTP_BASE_URL}",
"defaultHeaders": {
"x-client": "dispatch",
"x-context": "${env.DISPATCH_HTTP_X_CONTEXT}"
}
}
}If your module intentionally relies on job-level HTTP config, make the requirement explicit:
{
"dependencies": {
"http": {
"required": ["baseUrl", "defaultHeaders.x-client"]
}
}
}That keeps transport ownership at the job level while making missing config fail as a preflight error instead of a later request-time surprise.
In practice, the setup usually looks like this:
direnvloads repo-local values into the shell- the job resolves
httpfrom${env.*} - the job resolves secrets from
credentials.<name>.fromEnv - the module reads
ctx.httpandctx.credential
That keeps module code free of project-specific env conventions.
For stable request config that is shared across multiple calls inside one handler/helper, derive a scoped client explicitly:
const api = ctx.http.withDefaults({
baseUrl: payload.baseUrl,
defaultHeaders: {
authorization: `Bearer ${payload.token}`,
'x-client': 'dispatch',
},
});
const me = await api.get('/me');This keeps auth/session continuity automatic while keeping shared request config explicit at both the job and handler levels.
If a login or setup action also generates a same-run workflow value that later steps need,
return it under exports so the job can reference step.<id>.exports.* without inventing
response fields or persisting secrets in memory.
Modules should define auth intent, not transport session mechanics.
Good examples:
admin.loginadmin.logoutadmin.meadmin.create-user
Avoid:
- reading raw
Set-Cookieheaders in module code - saving cookies into
memory - reading ad hoc env vars directly inside handlers when a credential profile would do
- reading
${env.*}values directly in module code instead of letting the job supplyhttp - building manual
Cookieheaders unless there is a very specific non-session need - rebuilding the same base URL and shared headers by hand for every request when a scoped
ctx.http.withDefaults(...)client would do
The transport handles the common session-cookie case:
- host/domain matching
- path matching
securecookies only overhttps- expiry handling
This is intentionally run-scoped session support, not a full long-lived auth profile system.
- Cookie values are redacted from curl previews and transport call logs.
- Request/response body artifact behavior is unchanged.
- Credential values are resolved at runtime from env-backed profiles and are not part of job payloads.
- If a workflow needs durable credentials later, that should be a separate explicit auth/profile feature, not
memory.