Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Common Session codes:
| `SESSION_REQUIRED` | A raw browser command needs a root Session selector. | Run `webcmd session create -f json`, then retry as `webcmd --session <session-id> browser ...`. |
| `INVALID_SESSION_SELECTOR` | The selector is not an opaque Webcmd Session ID. | Use an ID returned by `webcmd session create` or `webcmd session list`. |
| `SESSION_SELECTOR_POSITION` | `--session` was placed after the command name. | Move it before the command: `webcmd --session <session-id> browser ...`. |
| `SESSION_NOT_FOUND` | The selected Session is missing for the current Profile. | Run `webcmd session list -f json`; create a new Session if needed. |
| `SESSION_NOT_FOUND` | The selected Session is missing for the current Profile. Sessions are per profile. | If the Session exists under another profile, the error names it; retry with `webcmd --profile <owner> session list` or `... session close <id>`. Otherwise run `webcmd session list -f json` for the current profile; create a new Session if needed. |
| `INVALID_SESSION_LIMIT` | `session list --limit` is outside 1-100. | Retry with a limit from 1 to 100. |
| `SESSION_BUSY` | Another command is writing in the same Session or site scope. | Wait for the holder to finish; if it is dead, use `webcmd session close <session-id> --force` as the last resort. |
| `SESSION_PAUSED_FOR_HUMAN_HANDOFF` | The Session is waiting for user action such as sign-in. | Finish the action in the browser, then run the returned verifier before retrying. |
Expand Down
6 changes: 6 additions & 0 deletions skill-src/webcmd-browser/SKILL.src.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ Use `run` and inspect `page.frames()`; target the frame by URL/name and keep ifr
| `run` times out before returning | Increase `--timeout` only after checking whether the wait condition is wrong. |
| Write may have happened before timeout | Take a fresh snapshot before retrying. Avoid duplicate submissions. |
| `SESSION_REQUIRED` | Create a Session, then retry with root `--session <session-id>`. |
| `SESSION_NOT_FOUND` | Pass the same `--profile` used on `session create`. `session list` is per profile; the error names the owning profile when the id exists under another one. |
| `SESSION_BUSY` | Wait for the listed holder; if it is dead, `webcmd session close <session-id> --force` is the last resort. |
| `SESSION_PAUSED_FOR_HUMAN_HANDOFF` | Finish the handoff and run the returned verifier before retrying. |
| Login wall appears | Use the Authentication and human handoff recipe. |
Expand All @@ -296,4 +297,9 @@ Author-only. Stripped by litprompt, so it costs the running agent nothing.
Append one dated line whenever a correction lands, or whenever an approach
is tried and rejected. Record what was tried and why it failed, not just
what won.

- 2026-08-21: `session close`/`list` without `--profile` reported `Session not
found` even when the id existed under another profile, and the list output
did not read as profile-scoped. Name the owning profile in the error hint
and show `profileId` in `session list` output (#388).
-->
1 change: 1 addition & 0 deletions skills/webcmd-browser/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ Use `run` and inspect `page.frames()`; target the frame by URL/name and keep ifr
| `run` times out before returning | Increase `--timeout` only after checking whether the wait condition is wrong. |
| Write may have happened before timeout | Take a fresh snapshot before retrying. Avoid duplicate submissions. |
| `SESSION_REQUIRED` | Create a Session, then retry with root `--session <session-id>`. |
| `SESSION_NOT_FOUND` | Pass the same `--profile` used on `session create`. `session list` is per profile; the error names the owning profile when the id exists under another one. |
| `SESSION_BUSY` | Wait for the listed holder; if it is dead, `webcmd session close <session-id> --force` is the last resort. |
| `SESSION_PAUSED_FOR_HUMAN_HANDOFF` | Finish the handoff and run the returned verifier before retrying. |
| Login wall appears | Use the Authentication and human handoff recipe. |
Expand Down
9 changes: 8 additions & 1 deletion src/browser/sessions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ describe('LocalBrowserSessionStore', () => {
const store = new LocalBrowserSessionStore({ baseDir: tempDir(), idFactory: () => 'session_a' });
const created = store.create('profile_work');

expect(() => store.require('profile_other', created.id)).toThrowError(expect.objectContaining({ code: 'SESSION_NOT_FOUND' }));
expect(() => store.require('profile_other', created.id)).toThrowError(expect.objectContaining({
code: 'SESSION_NOT_FOUND',
hint: expect.stringContaining('webcmd --profile profile_work session close'),
}));
expect(() => store.require('profile_missing', 'session_zzzzzzzz-zzzz-4zzz-8zzz-zzzzzzzzzzzz')).toThrowError(expect.objectContaining({
code: 'SESSION_NOT_FOUND',
hint: expect.stringContaining('Sessions are per profile'),
}));
expect(() => store.find('profile_work', 'work')).toThrowError(expect.objectContaining({ code: 'INVALID_SESSION_SELECTOR' }));
});

Expand Down
17 changes: 13 additions & 4 deletions src/browser/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ type StateFile = { version: 1; sessions: BrowserSessionRecord[] };
const SESSION_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;

export class SessionNotFoundError extends CliError {
constructor(sessionId: string, profileId: string) {
constructor(sessionId: string, profileId: string, ownerProfileId?: string) {
const hint = ownerProfileId && ownerProfileId !== profileId
? `This Session belongs to profile ${ownerProfileId}. Retry with \`webcmd --profile ${ownerProfileId} session close ${sessionId}\`. List with \`webcmd --profile ${ownerProfileId} session list\`.`
: `Sessions are per profile. Run \`webcmd --profile ${profileId} session list\` or \`webcmd profile list\`, then pass the same \`--profile\` used on create.`;
super(
'SESSION_NOT_FOUND',
`Session not found: ${sessionId}`,
`Run \`webcmd --profile ${profileId} session list\` to choose an existing Session.`,
hint,
EXIT_CODES.EMPTY_RESULT,
);
}
Expand Down Expand Up @@ -84,7 +87,10 @@ export class LocalBrowserSessionStore {
requireSessionIdShape(id);
const state = this.load();
const record = state.sessions.find((row) => row.id === id && row.profileId === profileId);
if (!record) throw new SessionNotFoundError(id, profileId);
if (!record) {
const owner = state.sessions.find((row) => row.id === id);
throw new SessionNotFoundError(id, profileId, owner?.profileId);
}
this.touchRecord(state, record);
return { ...record };
}
Expand Down Expand Up @@ -176,7 +182,10 @@ export class LocalBrowserSessionStore {
private requireMutable(state: StateFile, profileId: string, sessionId: string): BrowserSessionRecord {
requireSessionIdShape(sessionId);
const record = state.sessions.find((row) => row.id === sessionId && row.profileId === profileId);
if (!record) throw new SessionNotFoundError(sessionId, profileId);
if (!record) {
const owner = state.sessions.find((row) => row.id === sessionId);
throw new SessionNotFoundError(sessionId, profileId, owner?.profileId);
}
return record;
}

Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ export function createProgram(BUILTIN_CLIS: string, USER_CLIS: string, pluginsDi
console.log(`No browser Sessions found for Profile ${profileId}.`);
return;
}
await renderOutput(output, { fmt, fmtExplicit: outputFormatIsExplicit(command), columns: ['id', 'kind', 'runtimeState', 'handoff'] });
await renderOutput(output, { fmt, fmtExplicit: outputFormatIsExplicit(command), columns: ['id', 'kind', 'profileId', 'runtimeState', 'handoff'] });
});

const sessionCloseCmd = addOutputFormatOption(sessionCmd
Expand Down
1 change: 1 addition & 0 deletions src/skills.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ describe('webcmd skills content', () => {
expect(browser).toMatch(/Profiles are cookie jars[\s\S]{0,180}sessions are browser workspaces\/windows/i);
expect(browser).toMatch(/Parallel agents use separate sessions/i);
expect(browser).toContain('SESSION_BUSY');
expect(browser).toContain('SESSION_NOT_FOUND');
expect(browser).toContain('SESSION_PAUSED_FOR_HUMAN_HANDOFF');
expect(browser).toContain('webcmd session close <session-id> --force');
for (const skill of [usage, browser, autofix]) {
Expand Down
Loading