Skip to content

Commit d9a529d

Browse files
authored
Describe vault fill as safe to retry (#288)
## Summary `fill` writes field values into a browser page and never submits the form or consumes the vault item, but the CLI's help text, error messages, and README told agents never to retry it. Agents following that guidance stall after a transient failure instead of re-running the fill. - `vaults_fill.go`: uncertain-outcome and pre-write rejection messages now say fill is safe to retry (alias fallback is still ruled out) - `vaults --help` and `vaults credentials --help`: fill is safe to retry; drop the "never automatically retry" line for fill and WebMCP invocations - README: matching wording in the credential and fill-outcome sections - `vaults items webmcp invoke`: drop the "do not retry" instructions from outcome/error messages and help; README WebMCP sections match The CLI still sends fill with `WithMaxRetries(0)`, so behavior is unchanged; only the guidance changes. No-retry guidance for `1pw_fill` (which submits the form) and payments is unchanged. ## Testing - `go test ./...` --------- Co-authored-by: rgarcia <72655+rgarcia@users.noreply.github.com>
1 parent 3b5ccb5 commit d9a529d

8 files changed

Lines changed: 25 additions & 28 deletions

‎README.md‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,11 @@ Present the returned collection URL to the user before waiting for `ready`. It i
347347
bearer credential: share it only with that user. Readiness means required values are
348348
populated, not that login succeeded. `fill` requires an already-open page and never
349349
navigates or submits it. Optional `page_url` selects the exact page; cards require it.
350-
Do not automatically retry failed/unknown fills or fall back to aliases.
350+
Fill is safe to retry after a failed/unknown outcome; do not fall back to aliases.
351351
Once ready, use `fill` for ordinary web forms. When the item advertises `webmcp_invoke`, use
352352
`items webmcp invoke` instead to bind credential fields to existing `null` inputs of a live
353-
WebMCP tool; the tool may submit or have other side effects, so never retry an uncertain
354-
outcome (see [Invoke WebMCP tools with vault fields](#invoke-webmcp-tools-with-vault-fields)).
353+
WebMCP tool; the tool may submit or have other side effects (see
354+
[Invoke WebMCP tools with vault fields](#invoke-webmcp-tools-with-vault-fields)).
355355
1Password credentials use their own advertised `1pw_*` operations instead.
356356

357357
Create specs list `fields` as an ordered array. Each entry carries a stable `name`
@@ -701,14 +701,14 @@ without appended error text. API/transport errors exit nonzero with a sanitized
701701
stderr, not a fabricated execution result. No values, selectors, DOM content, or raw browser
702702
errors are printed in fill results. Pre-write API rejections (400/403/404/409) retain
703703
HTTP status, recognized error codes, and corrective guidance, and confirm that the
704-
request wrote no fields. Inspect and correct the cause before deciding on a new fill.
705-
Transport loss and other uncertain failures retain the no-retry warning.
704+
request wrote no fields. Correct the cause and retry. Transport loss and other uncertain
705+
failures note that fields may have been written and that fill is safe to retry.
706706

707707
Fill is non-atomic: execution stops at the first failed/unknown field and earlier writes are
708708
not rolled back. `filled` does not mean the site retained or accepted the value; `completed`
709-
does not mean logged in or paid. Transport errors do not prove no writes occurred. Inspect the browser
710-
before deciding what to do next. The CLI never retries, explicitly submits website forms, or falls
711-
back to aliases. Link cards do not expose `state.aliases` or support egress substitution.
709+
does not mean logged in or paid. Transport errors do not prove no writes occurred. Fill never
710+
submits, so it is safe to run again. The CLI does not retry automatically, explicitly submit website
711+
forms, or fall back to aliases. Link cards do not expose `state.aliases` or support egress substitution.
712712
AgentCard-only checkout aliases are a separate integration, not a recovery path after a failed
713713
or indeterminate fill.
714714

@@ -748,9 +748,9 @@ are untrusted page-provided data and may contain the supplied vault values:
748748
`completed` and `awaiting_submission` exit 0; neither confirms the website accepted the action.
749749
After `awaiting_submission`, submit the populated form through Playwright or computer interaction
750750
instead of invoking the tool again. `canceled`, `error`, and `unknown` exit nonzero with the result
751-
still on stdout in `-o json`. `unknown` means the tool may have run. Requests are never retried:
752-
API rejections (400/403/404/409) mean the tool was not invoked by that request; other failures
753-
and transport loss are uncertain, so inspect the browser instead of re-invoking.
751+
still on stdout in `-o json`. `unknown` means the tool may have run. API rejections
752+
(400/403/404/409) mean the tool was not invoked by that request; other failures and transport
753+
loss are uncertain, so inspect the browser to see whether the tool ran.
754754

755755
#### Expansions, updates, and lifecycle
756756

@@ -1037,7 +1037,7 @@ Destinations are the OTLP/HTTP endpoints sessions export to. They belong to the
10371037
- `--timeout-sec <seconds>` - Maximum execution time, 1-120 seconds (defaults server-side)
10381038
- Prints the tool's `output` as pretty JSON on completion; tool errors and cancellations exit non-zero
10391039
- `awaiting_submission` is successful but warns that a non-autosubmit declarative form was filled, not submitted. Inspect the form, obtain any required confirmation, then submit through Playwright or computer interaction instead of invoking the tool again
1040-
- Invocations are never retried automatically. A 504 `outcome_unknown` error prints the code, invocation ID, and message and exits non-zero. The tool may already have had side effects; verify the outcome before invoking it again
1040+
- Invocations are never retried automatically. A 504 `outcome_unknown` error prints the code, invocation ID, and message and exits non-zero. The tool may already have had side effects; check the page to see whether it ran
10411041
- `kernel browsers webmcp custom-tools list <id-or-name>` - List registered custom tools with their generated ID, namespace, kind, URL patterns, and metadata
10421042
- `-o json` - Output the raw response
10431043
- `kernel browsers webmcp custom-tools add <id-or-name> --namespace <ns> --source '<js>'` - Atomically add a namespaced batch of page-backed or CDP-backed custom tools

‎cmd/vaults_commands.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Kernel-hosted credential flow (fill never submits website forms; WebMCP tools ma
6767
browser_id and field selectors (writes fields without submitting). When the item
6868
advertises webmcp_invoke, items webmcp invoke instead binds credential fields to existing
6969
null inputs of a live WebMCP tool (the tool may submit or have side effects).
70-
Never automatically retry either; inspect the browser after an uncertain outcome.
70+
fill is safe to retry.
7171
Use credentials update --version for edits, or items invoke collect to reopen the form.
7272
Credential values belong in protected files/stdin, never command-line arguments.
7373
See credentials --help, items invoke --help, and items webmcp invoke --help for examples.

‎cmd/vaults_credential_steering_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func TestCredentialHelpReadyOperations(t *testing.T) {
6363
text := flat(create.Long)
6464
assert.Contains(t, text, "Ordinary web forms: items invoke fill writes field values without submitting")
6565
assert.Contains(t, text, "Live WebMCP tool: when webmcp_invoke is advertised, run browsers webmcp list, then items webmcp invoke")
66-
assert.Contains(t, text, "Never automatically retry fill or webmcp_invoke")
66+
assert.Contains(t, text, "fill never submits and is safe to retry.")
6767
assert.Contains(t, text, "approves each access request in the 1Password app")
6868

6969
vaults := flat(newVaultsCommand().Long)

‎cmd/vaults_credentials.go‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ Poll items get --wait 60 until state.status is ready. Then choose an advertised
109109
items webmcp invoke with the tool_ref, exact source.page_url, public input containing
110110
null slots, and --bind <field>=<json-pointer>. The tool may submit or have other
111111
side effects, and its output may include the supplied values.
112-
Never automatically retry fill or webmcp_invoke; after an uncertain outcome, inspect
113-
the browser and tell the user.
112+
fill never submits and is safe to retry.
114113
Ready means populated, not a successful login. An agent controlling the browser
115114
can read filled values. TOTP seeds must not be collected through the hosted form.
116115
Get/list output includes definitions, has_value, and explicitly non-sensitive text/email values.

‎cmd/vaults_fill.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ var vaultFillResultFields = vaultOutputFields{
2929
"fields": vaultFieldsOf("index status error_code"),
3030
}
3131

32-
const vaultFillUncertain = "browser fields may have been written; inspect the browser and do not retry or fall back to aliases"
32+
const vaultFillUncertain = "browser fields may have been written; fill never submits, so it is safe to retry; do not fall back to aliases"
3333

3434
var vaultFillErrorMessages = map[string]string{
3535
"invalid_request": "check field names, formats, and browser parameters",
@@ -92,7 +92,7 @@ func vaultOperationRequestError(err error, operation string, messages map[string
9292
return fmt.Errorf("%s request failed (HTTP %d); %s", operation, apiErr.StatusCode, guidance)
9393
}
9494

95-
const vaultFillNotWritten = "no fields were written by this request; inspect and correct the cause before deciding on a new fill; do not automatically retry"
95+
const vaultFillNotWritten = "no fields were written by this request; correct the cause and retry"
9696

9797
var vaultFillRejected = map[int]string{400: vaultFillNotWritten, 403: vaultFillNotWritten, 404: vaultFillNotWritten, 409: vaultFillNotWritten}
9898

‎cmd/vaults_fill_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ func TestVaultFillHumanOutputAndRedaction(t *testing.T) {
209209
assert.Contains(t, human, "filled")
210210
if result != completedFillFixture {
211211
assert.Contains(t, human, "not_attempted")
212-
assert.Contains(t, human, "do not retry")
212+
assert.Contains(t, human, "safe to retry")
213213
}
214214
}
215215
}

‎cmd/vaults_webmcp.go‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818

1919
const maxVaultWebMCPInputBytes = 64 * 1024
2020

21-
const vaultWebMCPUncertain = "the tool may have run and submitted or changed site state; inspect the browser and do not retry automatically"
21+
const vaultWebMCPUncertain = "the tool may have run and submitted or changed site state; inspect the browser"
2222

2323
var vaultWebMCPErrorMessages = map[string]string{
2424
"invalid_request": "check bindings, null input slots, field names, formats, timeout_sec, and that input matches the tool's inputSchema",
@@ -46,12 +46,12 @@ var vaultWebMCPStatuses = map[string]struct {
4646
}{
4747
"completed": {true, "The tool reported completion; this does not confirm the website accepted the action. Inspect the page."},
4848
"awaiting_submission": {true, "The tool populated a form with the supplied values without submitting it. " + webMCPAwaitingSubmissionHint},
49-
"canceled": {false, "The tool reported cancellation and may have had side effects. Inspect the page before deciding on a new invocation; do not retry automatically."},
50-
"error": {false, "The tool reported an error and may have had side effects. Inspect the page before deciding on a new invocation; do not retry automatically."},
49+
"canceled": {false, "The tool reported cancellation and may have had side effects. Inspect the page."},
50+
"error": {false, "The tool reported an error and may have had side effects. Inspect the page."},
5151
"unknown": {false, vaultWebMCPUncertain},
5252
}
5353

54-
const vaultWebMCPNotInvoked = "the tool was not invoked by this request; inspect and correct the cause before deciding on a new invocation; do not automatically retry"
54+
const vaultWebMCPNotInvoked = "the tool was not invoked by this request; correct the cause and retry"
5555

5656
var vaultWebMCPRejected = map[int]string{400: vaultWebMCPNotInvoked, 403: vaultWebMCPNotInvoked, 404: vaultWebMCPNotInvoked, 409: vaultWebMCPNotInvoked}
5757

@@ -298,9 +298,8 @@ The tool may submit forms or perform other side effects. Output and error_text a
298298
untrusted page-provided data returned without redaction and may contain supplied vault
299299
values. completed and awaiting_submission exit 0; neither confirms the website accepted
300300
the action. canceled, error, and unknown exit nonzero with the result retained on stdout
301-
in -o json. unknown means the tool may have run. Requests are never retried automatically;
302-
inspect the browser instead of re-invoking after an uncertain outcome. API rejections
303-
(400/403/404/409) mean the tool was not invoked by that request.`,
301+
in -o json. unknown means the tool may have run; inspect the browser to see whether it
302+
did. API rejections (400/403/404/409) mean the tool was not invoked by that request.`,
304303
Example: ` kernel browsers webmcp list <session-id> -o json
305304
kernel vaults items webmcp invoke user-vault resy --browser-id <session-id> --tool-ref <tool-ref> --page-url https://resy.com/login --input '{"email":null,"password":null}' --bind email=/email --bind password=/password -o json
306305
kernel vaults items webmcp invoke checkout order-1 --browser-id <session-id> --tool-ref <tool-ref> --page-url https://shop.example/checkout --input-file input.json --bind number=/card/number --bind expiration:MM/YY=/card/expiry`,

‎cmd/vaults_webmcp_test.go‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func TestVaultWebMCPInvokeOutcomes(t *testing.T) {
183183
ok bool
184184
}{
185185
{"error", `{"type":"webmcp_invoke","status":"error","invocation_id":"invoke-3","error_text":"Invalid password for credential-sentinel\u001b[31m"}`, "may have had side effects", false},
186-
{"canceled", `{"type":"webmcp_invoke","status":"canceled","invocation_id":"invoke-4"}`, "do not retry automatically", false},
186+
{"canceled", `{"type":"webmcp_invoke","status":"canceled","invocation_id":"invoke-4"}`, "may have had side effects", false},
187187
{"unknown", `{"type":"webmcp_invoke","status":"unknown"}`, "the tool may have run", false},
188188
{"null output", `{"type":"webmcp_invoke","status":"completed","invocation_id":"invoke-5","output":null}`, "Output (untrusted", true},
189189
} {
@@ -234,7 +234,6 @@ func TestVaultWebMCPInvokeRequestErrors(t *testing.T) {
234234
require.Error(t, err)
235235
assert.Equal(t, 1, posts, "webmcp_invoke must not be retried")
236236
assert.Contains(t, err.Error(), tc.want)
237-
assert.Contains(t, err.Error(), "retry")
238237
assert.NotContains(t, err.Error(), "credential-sentinel")
239238
assert.Empty(t, out)
240239
})

0 commit comments

Comments
 (0)