Never guess credentials — ask the user, or read them from knowledge - #128
Open
DavertMik wants to merge 3 commits into
Open
Never guess credentials — ask the user, or read them from knowledge#128DavertMik wants to merge 3 commits into
DavertMik wants to merge 3 commits into
Conversation
A redirect to a login form was being treated as a form to fill: the model invented an email and a password and spent its remaining attempts on a login that could not succeed. The system prompt already told it to ask instead, but resolveState exposed no way to ask, so guessing was the only move available. The Navigator now gets askUser, and the resolution loop survives a turn that only asks — previously an answer arrived after the loop had already given up and discarded it. askUser also takes `persistent`: a lasting fact about the page (how to authorize, what its data means) is appended to the knowledge of that page, so later runs read it instead of asking again. It is filed under the page path with the query string dropped, so it still matches when the redirect carries a different message. The tool is only offered where somebody can answer it; the Pilot's inline copy is now the same shared one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DenysKuchma
requested changes
Aug 21, 2026
| const path = extractStatePath(state.url || state.fullUrl || '/') | ||
| .split('?')[0] | ||
| .split('#')[0]; | ||
| const { filePath } = stateManager.getKnowledgeTracker().addKnowledge(path, `${question}\n\n${userInput}`); |
Collaborator
There was a problem hiding this comment.
This could silently save login credentials to knowledge/*.md. The model decides whether persistent is true, while the user isn’t told that their answer will be written to a file. I’d either ask for explicit permission before saving it or avoid automatically persisting credential-related answers
Contributor
Author
There was a problem hiding this comment.
Agreed, we already have --session so we can avoid persistent storage
askUser wrote knowledge on its own: a `persistent` flag appended the question and the answer to the knowledge file of the page, decided by the model mid-run, with nobody to check whether the answer had been any use. The tool now writes nothing. It records the answer against the page it was asked on and starts capturing the log, so what the answer produced is on record. Once the work that needed it has finished — a test, or the command that was running — the Captain reads that log and judges one thing: was the answer worth more than this run? When it was, the question and the answer are kept verbatim in the experience of that page, under a how-to heading the Captain supplies; the recipe itself is already written by the Navigator, so nothing is retyped by a model. Nothing is written when the log does not show the answer being used, when what followed it failed, or when the page already carries the fact. Experience is filed per page state, but a test only ever saw the experience of the page it started on, and the Pilot reviewing a new page had neither its table of contents nor learnExperience. Both now arrive with the page — without which an answer saved for a login page would never reach the run that lands there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…es-credentials # Conflicts: # CHANGELOG.md # tests/unit/tools.test.ts
Collaborator
|
Credentials are still persisted, just in experience now. Prompt answers aren't registered as secrets, so redactSecrets() won't hide passwords. Credential answers should stay session-only or be redacted before writeFlow() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What happened
A page that redirected to a login form was treated as a form to fill. The model invented an email and a password, submitted them, and spent the rest of its attempts on a login that was never going to succeed:
The system prompt already said to ask the user for credentials — but
resolveStateexposed onlystop. The instruction had no mechanism behind it, so guessing was the only move on the table.Changes
Credentials have two sources, and neither is invention. The
<credentials>block in the Navigator prompt rules out made-up values for anything that identifies an account. A scenario that explicitly creates something, such as registering a new user, still makes up the values it needs.The Navigator can ask.
askUseris now among the tools inresolveState, so credentials are requested at the moment they are needed and the run carries on with the answer. Where nobody can answer, it stops and names what is missing — the failure path already points atexplorbot learn "<path>"for recording them once.The loop survives an ask-only turn. A turn that called a tool and returned no code used to end the loop, so the user's answer arrived after the Navigator had given up and was discarded (
src/ai/navigator.ts, one line).An answer can become knowledge.
askUsertakespersistent: a lasting fact about the page is appended to that page's knowledge file, and later runs read it instead of asking again. It is filed under the page path with the query string dropped —normalizeUrlonly trims, andmatchesUrlkeeps a query in the compared path whenever the pattern has one, so filing under/users/sign_in?info=You+must+be+logged+in...would have written a file no later visit could match.The tool is offered only where somebody can answer it. The Pilot's inline copy became the shared
createAskUserTool, and both call sites gate onisInteractive()rather than handing the model a tool whose only possible reply was "user input not available".Tests
Two paths that could silently not work are covered: the ask → fill round trip in
navigator-resolve-state.test.ts(verified to fail without the loop fix — the first version of it passed for the wrong reason, via the end-of-loop fallback), and persistence intools.test.ts, where the fake state carries a query string so the path stripping stays locked in.Unit 1026 pass / 3 fail, integration 80 pass / 0 fail. The 3 failures are pre-existing and untracked on
main—navigator-user-redirect.test.tsandexecution-controller-resume.test.tstest a<user_redirect>feature that does not exist innavigator.ts; confirmed by stashing this branch's changes.Known, not fixed
---separator.addKnowledgehas always appended rather than deduped.askUseranswers are not passed toregisterSecret, so a password typed at the prompt is written verbatim into the experience file when the successful login flow is recorded.LearnCommandpassesstate.urltoaddKnowledgeunstripped and has the same query-string issue; left alone as out of scope.🤖 Generated with Claude Code