collect_secret
Gets the values a task needs written where the project reads them, typed by a human or generated. The call opens the page and waits there while the human types, then returns the key names and the file, never the values.
{
"secrets": [
{ "name": "RESEND_API_KEY", "provider": "resend" },
{ "name": "AUTH_SECRET", "generate": {} }
],
"reason": "Sending the sign-in emails needs a **Resend** key, and sessions need a signing secret.",
"sink": { "kind": "dotenv", "path": ".env.local" }
}Input
| Field | Type | What it is |
|---|---|---|
secrets | array, 1 to 12 | Every key the task needs, in one request. See the entries |
reason | string, 1 to 2,000 characters | Why the values are needed, shown on the page as the agent's claim. Markdown: bold, italics, code, lists, quotes. Links show as text |
sink | object | Where the values are written. See Sinks |
sandbox | boolean, optional | true only when the agent's environment tells it that it runs in a cloud sandbox, a container or a remote VM. See Cloud sandboxes |
secrets
Each entry is one key. It takes at most one of generate, provider and format;
Asking for values shows what each looks like on the page.
| Field | Type | What it is |
|---|---|---|
name | string | The variable the value is stored under, and the field's label. SCREAMING_SNAKE_CASE, matching [A-Z][A-Z0-9_]*, up to 128 characters |
generate | object, optional | A random value, made on the page. bytes: 16 to 64, default 32. encoding: base64url, base64 or hex, default base64url. A key the file already holds is kept |
provider | string, optional | A key the human gets from a provider: a provider, such as stripe, or one of its keys, such as stripe/webhook_secret. Every accepted value is in Providers |
format | string, optional | What the value is, when no one provider issues it: one of the formats, such as postgres_url |
secret | boolean, optional | false shows the value in the clear, for one that is not secret |
caption | string, optional, 1 to 90 characters | One short line under the field, such as where to find the value |
A key with none of generate, provider and format is plain text, unless its name is one a
provider's key conventionally goes by.
Output
| Field | Type | What it is |
|---|---|---|
tell_user | string, optional | First, once written: the line the agent writes to the human straight away, such as Fingerprint ๐ ๐ฅ ๐ฎ ๐ง |
request_id | string | The request, for await_secret and cancel_secret |
status | "awaiting", "written" or "expired" | Where the request stands |
expires_in | number | Seconds the request has left |
names | string[] | The keys asked for |
kept | string[], optional | Generated keys the file already held, left as they were |
emoji | string, optional | The fingerprint of the values as the file holds them, such as ๐ ๐ฅ ๐ฎ ๐ง |
file_keys | string[], optional | Every key the file holds after the write, names only |
sink | { kind, path } | The file, relative to the project |
note | string | What to do next, written for the agent |
url | string, optional | A link the human has to open, when offprompt could not open the page itself |
The result travels both as structured content and as the same JSON in a text block, so an agent
that reads either sees the same thing. A written result's text opens with one more line, for hosts
that show the result's text rather than its structure, such as
Wrote 1 value to .env ยท fingerprint ๐ ๐ฅ ๐ฎ ๐ง.
What the call does
Checks the request
offprompt finds the project, resolves the sink inside it and resolves each key. The first problem refuses the whole request, with a message saying what to fix. See Refusals.
Writes at once, when nothing needs typing
When every key is generated, no page opens: offprompt makes the values, writes them and returns
written.
Opens the page and waits
Otherwise, on the human's machine, it offers the page in the host's own URL dialog, where the host takes one, then opens it in the human's browser if the dialog is refused or there is none. It waits, for up to 300 seconds, until the values are written or the request expires. There is nothing to poll and nothing to do meanwhile.
Or returns a link
From a cloud sandbox it always returns at once with
status: "awaiting", and with a url unless the host is showing the tunnel's link in its own
dialog. On the human's machine it returns at once with a url only when neither the dialog nor
the browser worked. The agent shows a url to the human on its own line and calls
await_secret.
A value that fails its checks leaves the request open for another try, however many times. One successful write closes it.
What the agent is told
The tool's description tells the agent to:
- reference the values by name from then on, such as
process.env.RESEND_API_KEY; - never open,
cat,grepor print the file: reading it puts the values into the transcript, andfile_keysalready says what it holds; - write "Fingerprint" and the four emoji to the human as soon as the call returns, before its next tool call, so they can check them against the page while it is still open;
- show a
url, when the result carries one, in its reply on its own line, since nothing happens until the human opens it.
Each result's note repeats what applies to it: a written result's carries the first three, and
a result with a url the last.
Refusals
A refused request is an error result that begins offprompt refused that request:, and no page
opens. The causes:
| Cause | Example |
|---|---|
| No project to write to | offprompt was started outside a project and the host named no workspace |
| A sink path that leaves the project | ../.env, or a symlink out of it |
| A sink path that is the project folder, or a folder | . |
Several keys for a file sink | A file sink holds exactly one value |
| A key asked for twice | RESEND_API_KEY twice |
| More than one way for one key | generate and provider together |
| A provider's key the name does not settle | provider: "stripe" for PAYMENTS_KEY, where Stripe issues several |
| Generated keys alone, for a git-tracked file | Only the human can allow that write, on the page |
The tool's input schema refuses a name that is not SCREAMING_SNAKE_CASE, and a provider or
format that is not in the registry, before offprompt sees the call.
Timeouts
A request lives 300 seconds, or 30 minutes when made from a cloud sandbox, where the human may not be watching when the link appears. offprompt's config for Claude Code, Codex and Pi gives the call 330 seconds, so the agent's own tool timeout does not end it while the human is typing. Cursor's sets none, and Cursor applies its own default.
If the human interrupts the waiting call, the page stays live for the rest of the request's time,
and typing the values still writes them. await_secret picks the request up again.