Skip to content

feat(cli): add session export to markdown#51105

Closed
mvanhorn wants to merge 2 commits intoopenclaw:mainfrom
mvanhorn:feat/session-export-md
Closed

feat(cli): add session export to markdown#51105
mvanhorn wants to merge 2 commits intoopenclaw:mainfrom
mvanhorn:feat/session-export-md

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

Summary

Adds openclaw sessions export - a CLI subcommand that converts session JSONL transcripts to clean markdown or pretty-printed JSON. Tool calls and tool results are collapsed into <details> blocks. Long tool results are truncated at 500 characters.

Why this matters

The existing /export-session slash command generates HTML, but it has been broken for months because Prettier keeps corrupting the template's JS placeholders:

Issue Problem
#41862 export-session HTML renders blank
#22595 /export-session generates broken HTML
#49957 Session export HTML empty due to reformatted placeholders

Five open PRs are trying to fix the HTML template corruption. This PR takes a different approach: plain markdown output with no template placeholders, so there is nothing for formatters to break.

Users also want portable session history (#50701 - WebUI history resets after updates, #50865 - access to old sessions). A CLI export to markdown gives users a working way to archive conversations.

Changes

  • src/commands/session-export.ts: New command that reads session JSONL via SessionManager.open(), walks SessionMessageEntry entries, and formats them as markdown with timestamps and speaker labels. User messages shown directly, assistant text blocks shown, tool calls collapsed into <details> with the tool name and arguments, tool results collapsed with truncation. Also supports --format json for raw output.
  • src/commands/session-export.test.ts: 10 vitest tests covering empty sessions, user/assistant exchanges, tool calls, tool results, error results, image content, compaction markers, long result truncation, and null headers.
  • src/cli/program/register.status-health-sessions.ts: Registers sessions export as a subcommand alongside sessions cleanup. Accepts --session, --agent, --format, --output flags.

Usage

openclaw sessions export --session main
openclaw sessions export --session main --format json
openclaw sessions export --session main --output chat.md
openclaw sessions export --session main --agent work

Testing

pnpm test -- src/commands/session-export.test.ts
# 10 passed

All 10 tests pass. Files pass oxfmt format check and oxlint individually. Note: pnpm tsgo has pre-existing type errors in extensions/msteams/ on upstream/main that are unrelated to this change.

This contribution was developed with AI assistance (Claude Code).

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Mar 20, 2026

🔒 Aisle Security Analysis

We found 3 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Terminal escape/control sequence injection via session export to stdout
2 🔵 Low Markdown/HTML injection in session markdown export (potential XSS when rendered)
3 🔵 Low Session export writes transcripts with default (potentially world-readable) file permissions

1. 🟡 Terminal escape/control sequence injection via session export to stdout

Property Value
Severity Medium
CWE CWE-150
Location src/commands/session-export.ts:231-233

Description

The sessions export command writes exported session transcripts (user/assistant/tool output) directly to the terminal when --output is not provided.

  • Session content includes untrusted text such as user messages and tool results (extractUserText, extractAssistantText, extractToolResultText).
  • When no output file is specified, the command emits the generated markdown/JSON via process.stdout.write(result).
  • If any stored session content contains ANSI escape sequences / terminal control codes (e.g., OSC 52 clipboard, cursor movement, screen clearing), printing it to an interactive TTY can lead to terminal spoofing, clipboard manipulation, or terminal state changes.

Vulnerable code:

} else {
  process.stdout.write(result);
}

Recommendation

Only emit raw session content when stdout is not a TTY (piped/redirected), and sanitize/strip control sequences for interactive terminals.

Example fix:

import stripAnsi from "strip-ansi";// ...
const isTty = Boolean(process.stdout.isTTY);
const safeForTerminal = isTty
  ? stripAnsi(result).replace(/[\u0000-\u001F\u007F-\u009F]/g, "")
  : result;

process.stdout.write(safeForTerminal);

Alternatively, add an explicit --raw flag to allow unsafe output when the user opts in, and default to safe output for TTYs.


2. 🔵 Markdown/HTML injection in session markdown export (potential XSS when rendered)

Property Value
Severity Low
CWE CWE-79
Location src/commands/session-export.ts:134-168

Description

The sessionEntriesToMarkdown() exporter writes untrusted session content (user/assistant/tool text and tool names) directly into Markdown, and also emits raw HTML (<details>, <summary>) with interpolated values.

If the exported .md is later rendered by a Markdown renderer that allows raw HTML (common in many previewers, static site generators, and web apps), an attacker can inject HTML/JS (e.g., <img onerror=...>, <script>...</script>, or closing tags like </summary>...) via:

  • UserMessage.content / AssistantMessage.content text blocks (written as-is into the markdown body)
  • block.name from assistant tool calls (inserted into an HTML <summary> tag)
  • msg.toolName from tool results (inserted into an HTML <summary> tag)

Vulnerable code:

lines.push(text); // user/assistant/tool text emitted raw
...
lines.push(`<summary>Tool call: ${tc.name}</summary>`); // raw HTML with unescaped name
...
lines.push(`<summary>Tool result: ${msg.toolName}${msg.isError ? " (error)" : ""}</summary>`);

Only triple backticks are escaped inside code fences; HTML special characters are not escaped/sanitized.

Recommendation

Escape/sanitize untrusted content before embedding it into Markdown/HTML.

Option A (recommended): avoid raw HTML entirely and use pure Markdown constructs (headings, blockquotes, fenced code blocks) for tool calls/results.

Option B: if keeping <details>/<summary>, HTML-escape interpolated values and also escape message text when not in code blocks.

Example HTML escaping helper:

function escapeHtml(s: string): string {
  return s
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/\"/g, "&quot;")
    .replace(/'/g, "&#39;");
}// When outputting raw text:
lines.push(escapeHtml(text));// When interpolating into HTML:
lines.push(`<summary>Tool call: ${escapeHtml(tc.name)}</summary>`);
lines.push(`<summary>Tool result: ${escapeHtml(msg.toolName)}${msg.isError ? " (error)" : ""}</summary>`);

Also consider a --no-html / --safe export mode that escapes all HTML/Markdown metacharacters to produce a "plain text" transcript.


3. 🔵 Session export writes transcripts with default (potentially world-readable) file permissions

Property Value
Severity Low
CWE CWE-732
Location src/commands/session-export.ts:226-230

Description

The sessionExportCommand exports full session transcripts (which may include secrets, tokens, personal data, or tool outputs) to a user-specified path using fs.writeFileSync() without setting restrictive file permissions.

  • On many systems the default file mode is affected by process umask (often 022), resulting in files like 0644 (world-readable).
  • This can expose sensitive session contents to other local users on shared/multi-user systems.

Vulnerable code:

fs.writeFileSync(outputPath, result, "utf-8");

Recommendation

Write exported transcripts with restrictive permissions (and consider creating directories with restrictive perms too).

Example fix:

if (!fs.existsSync(outputDir)) {
  fs.mkdirSync(outputDir, { recursive: true, mode: 0o700 });
}

fs.writeFileSync(outputPath, result, { encoding: "utf-8", mode: 0o600 });

This prevents other local users from reading exported transcripts by default.


Analyzed PR: #51105 at commit c2c916b

Last updated on: 2026-03-20T16:33:05Z

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation cli CLI command changes commands Command implementations size: M labels Mar 20, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c623ed205f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +58 to +60
for (const block of msg.content) {
if (block.type === "text") {
textParts.push(block.text);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle legacy assistant string content

sessionEntriesToMarkdown calls extractAssistantText, but that helper assumes msg.content is an array of typed blocks and iterates it directly. Existing transcript readers in this repo already handle assistant content as either string or array (for example extractPreviewText in src/gateway/session-utils.fs.ts), so legacy sessions with string assistant content will export as empty assistant turns. This drops real conversation text from exported history and makes the new command unreliable for older transcripts.

Useful? React with 👍 / 👎.

Comment on lines +153 to +155
lines.push("```");
lines.push(truncated);
lines.push("```");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Escape code-fence delimiters in tool result export

Tool results are inserted verbatim inside a triple-backtick fence, but the payload is not escaped. If a tool output includes ``` (common when tools return markdown or code snippets), the fence closes early and the rest of the export is malformed or reinterpreted as markdown. This breaks the primary output format for realistic transcripts; use a safer fence strategy (e.g., dynamic fence length) or an escaping approach before writing tool text.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 20, 2026

Greptile Summary

This PR adds openclaw sessions export, a new CLI subcommand that converts session JSONL transcripts to markdown or JSON — a practical workaround for the long-broken /export-session HTML command. The implementation is well-structured: a pure sessionEntriesToMarkdown function handles formatting (making it easy to unit-test), tool calls and results are collapsed into <details> blocks, and the command integrates cleanly into the existing sessions subcommand tree following established patterns.

A few issues to address before merging:

  • console.log instead of runtime.log (src/commands/session-export.ts:217): the success-path log bypasses the shouldEmitRuntimeLog() Vitest suppression guard defined in runtime.ts, breaking the pattern used everywhere else in this function.
  • Fragile tilde expansion (src/commands/session-export.ts:210): process.env.HOME ?? "" is undefined on Windows, causing the resolved path to fall back to the current directory. os.homedir() is the portable Node.js idiom.
  • Tool call summary omits tool name (src/commands/session-export.ts:134): <summary>Tool call</summary> makes every collapsed block anonymous, while tool result summaries correctly show the tool name. Users must expand each block to see what was invoked.
  • Truncation test assertion is indirect (src/commands/session-export.test.ts:160): md.length < longText.length passes today but would silently weaken if header boilerplate grows; a direct content check is more resilient.

Confidence Score: 3/5

  • Safe to merge after fixing the runtime.log consistency issue and the tilde expansion; the remaining comments are best-practice improvements.
  • The core logic is correct and the tests pass. The console.log / runtime.log mismatch is a real correctness issue for future test coverage of sessionExportCommand, and the os.homedir() fix is a portability correctness issue. Both are small, isolated changes. Downgraded from 4 because there are two genuine correctness issues that should be fixed before landing.
  • src/commands/session-export.ts — lines 210 and 217 need fixes before merge.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/commands/session-export.ts
Line: 217

Comment:
**`console.log` bypasses runtime abstraction and test suppression**

`runtime.log` should be used here instead of `console.log`. Looking at `src/runtime.ts`, `runtime.log` wraps `console.log` with a `shouldEmitRuntimeLog()` guard that suppresses output when running under Vitest (unless `console.log` is mocked or `OPENCLAW_TEST_RUNTIME_LOG=1`). Calling `console.log` directly skips this gate, so any future test that invokes `sessionExportCommand` with `--output` will emit noisy output to the test console. The rest of this function already uses `runtime.error` and `runtime.exit` — using `console.log` for the success path breaks that consistency.

```suggestion
    runtime.log(`Exported session "${sessionKey}" to ${outputPath}`);
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/commands/session-export.ts
Line: 209-211

Comment:
**Fragile tilde expansion — use `os.homedir()`**

`output.replace("~", process.env.HOME ?? "")` has two problems:

1. `process.env.HOME` is `undefined` on Windows, falling back to `""`, which causes `path.resolve("")` to resolve to the current working directory rather than the home directory. `os.homedir()` from Node's built-in `os` module is portable and always returns a valid path.
2. `String.replace(string, string)` only replaces the first occurrence. For the typical `~/path` pattern this is fine in practice, but the idiomatic form uses a regex anchored to the start: `/^~/`.

```suggestion
    const outputPath = path.resolve(
      output.startsWith("~") ? output.replace(/^~/, os.homedir()) : output,
    );
```

Also add `import os from "node:os";` at the top of the file.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/commands/session-export.ts
Line: 134-135

Comment:
**Tool call `<summary>` is missing the tool name**

Tool result summaries include the tool name (`Tool result: weather.get`), but tool call summaries just say `"Tool call"` — forcing the user to expand every `<details>` block to find out which tool was invoked. Since `extractAssistantText` already returns the tool name embedded inside the code string (`tc` starts with `${block.name}(`), it's straightforward to include the name in the summary for at-a-glance readability, consistent with how tool results are labelled.

One way to fix this is to change `extractAssistantText` to return structured objects instead of formatted strings:

```ts
// extractAssistantText returns { name: string; argsStr: string }[]
toolCalls.push({ name: block.name, argsStr: JSON.stringify(block.arguments, null, 2) });
```

Then in the rendering loop:
```ts
lines.push(`<summary>Tool call: ${tc.name}</summary>`);
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/commands/session-export.test.ts
Line: 159-160

Comment:
**Truncation assertion is indirect and may give false confidence**

`expect(md.length).toBeLessThan(longText.length)` passes because the truncated content + markdown boilerplate (~650 chars) is less than 1000 chars. This will break silently if the boilerplate ever grows past 1000 chars. A more direct assertion would verify that the raw tool result text is actually truncated to 500 characters:

```suggestion
    // Should not contain the full 1000 chars
    expect(md).not.toContain("x".repeat(501));
    expect(md).toContain("x".repeat(500) + "...");
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "feat(cli): add sessi..."

Comment thread src/commands/session-export.ts Outdated
fs.mkdirSync(outputDir, { recursive: true });
}
fs.writeFileSync(outputPath, result, "utf-8");
console.log(`Exported session "${sessionKey}" to ${outputPath}`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 console.log bypasses runtime abstraction and test suppression

runtime.log should be used here instead of console.log. Looking at src/runtime.ts, runtime.log wraps console.log with a shouldEmitRuntimeLog() guard that suppresses output when running under Vitest (unless console.log is mocked or OPENCLAW_TEST_RUNTIME_LOG=1). Calling console.log directly skips this gate, so any future test that invokes sessionExportCommand with --output will emit noisy output to the test console. The rest of this function already uses runtime.error and runtime.exit — using console.log for the success path breaks that consistency.

Suggested change
console.log(`Exported session "${sessionKey}" to ${outputPath}`);
runtime.log(`Exported session "${sessionKey}" to ${outputPath}`);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/session-export.ts
Line: 217

Comment:
**`console.log` bypasses runtime abstraction and test suppression**

`runtime.log` should be used here instead of `console.log`. Looking at `src/runtime.ts`, `runtime.log` wraps `console.log` with a `shouldEmitRuntimeLog()` guard that suppresses output when running under Vitest (unless `console.log` is mocked or `OPENCLAW_TEST_RUNTIME_LOG=1`). Calling `console.log` directly skips this gate, so any future test that invokes `sessionExportCommand` with `--output` will emit noisy output to the test console. The rest of this function already uses `runtime.error` and `runtime.exit` — using `console.log` for the success path breaks that consistency.

```suggestion
    runtime.log(`Exported session "${sessionKey}" to ${outputPath}`);
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +209 to +211
const outputPath = path.resolve(
output.startsWith("~") ? output.replace("~", process.env.HOME ?? "") : output,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fragile tilde expansion — use os.homedir()

output.replace("~", process.env.HOME ?? "") has two problems:

  1. process.env.HOME is undefined on Windows, falling back to "", which causes path.resolve("") to resolve to the current working directory rather than the home directory. os.homedir() from Node's built-in os module is portable and always returns a valid path.
  2. String.replace(string, string) only replaces the first occurrence. For the typical ~/path pattern this is fine in practice, but the idiomatic form uses a regex anchored to the start: /^~/.
Suggested change
const outputPath = path.resolve(
output.startsWith("~") ? output.replace("~", process.env.HOME ?? "") : output,
);
const outputPath = path.resolve(
output.startsWith("~") ? output.replace(/^~/, os.homedir()) : output,
);

Also add import os from "node:os"; at the top of the file.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/session-export.ts
Line: 209-211

Comment:
**Fragile tilde expansion — use `os.homedir()`**

`output.replace("~", process.env.HOME ?? "")` has two problems:

1. `process.env.HOME` is `undefined` on Windows, falling back to `""`, which causes `path.resolve("")` to resolve to the current working directory rather than the home directory. `os.homedir()` from Node's built-in `os` module is portable and always returns a valid path.
2. `String.replace(string, string)` only replaces the first occurrence. For the typical `~/path` pattern this is fine in practice, but the idiomatic form uses a regex anchored to the start: `/^~/`.

```suggestion
    const outputPath = path.resolve(
      output.startsWith("~") ? output.replace(/^~/, os.homedir()) : output,
    );
```

Also add `import os from "node:os";` at the top of the file.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/commands/session-export.ts Outdated
Comment on lines +134 to +135
lines.push("<details>");
lines.push(`<summary>Tool call</summary>`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Tool call <summary> is missing the tool name

Tool result summaries include the tool name (Tool result: weather.get), but tool call summaries just say "Tool call" — forcing the user to expand every <details> block to find out which tool was invoked. Since extractAssistantText already returns the tool name embedded inside the code string (tc starts with ${block.name}(), it's straightforward to include the name in the summary for at-a-glance readability, consistent with how tool results are labelled.

One way to fix this is to change extractAssistantText to return structured objects instead of formatted strings:

// extractAssistantText returns { name: string; argsStr: string }[]
toolCalls.push({ name: block.name, argsStr: JSON.stringify(block.arguments, null, 2) });

Then in the rendering loop:

lines.push(`<summary>Tool call: ${tc.name}</summary>`);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/session-export.ts
Line: 134-135

Comment:
**Tool call `<summary>` is missing the tool name**

Tool result summaries include the tool name (`Tool result: weather.get`), but tool call summaries just say `"Tool call"` — forcing the user to expand every `<details>` block to find out which tool was invoked. Since `extractAssistantText` already returns the tool name embedded inside the code string (`tc` starts with `${block.name}(`), it's straightforward to include the name in the summary for at-a-glance readability, consistent with how tool results are labelled.

One way to fix this is to change `extractAssistantText` to return structured objects instead of formatted strings:

```ts
// extractAssistantText returns { name: string; argsStr: string }[]
toolCalls.push({ name: block.name, argsStr: JSON.stringify(block.arguments, null, 2) });
```

Then in the rendering loop:
```ts
lines.push(`<summary>Tool call: ${tc.name}</summary>`);
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/commands/session-export.test.ts Outdated
Comment on lines +159 to +160
// Should not contain the full 1000 chars
expect(md.length).toBeLessThan(longText.length);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Truncation assertion is indirect and may give false confidence

expect(md.length).toBeLessThan(longText.length) passes because the truncated content + markdown boilerplate (~650 chars) is less than 1000 chars. This will break silently if the boilerplate ever grows past 1000 chars. A more direct assertion would verify that the raw tool result text is actually truncated to 500 characters:

Suggested change
// Should not contain the full 1000 chars
expect(md.length).toBeLessThan(longText.length);
// Should not contain the full 1000 chars
expect(md).not.toContain("x".repeat(501));
expect(md).toContain("x".repeat(500) + "...");
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/session-export.test.ts
Line: 159-160

Comment:
**Truncation assertion is indirect and may give false confidence**

`expect(md.length).toBeLessThan(longText.length)` passes because the truncated content + markdown boilerplate (~650 chars) is less than 1000 chars. This will break silently if the boilerplate ever grows past 1000 chars. A more direct assertion would verify that the raw tool result text is actually truncated to 500 characters:

```suggestion
    // Should not contain the full 1000 chars
    expect(md).not.toContain("x".repeat(501));
    expect(md).toContain("x".repeat(500) + "...");
```

How can I resolve this? If you propose a fix, please make it concise.

mvanhorn and others added 2 commits March 20, 2026 09:12
Add `openclaw sessions export` subcommand that converts session JSONL
transcripts to clean markdown or JSON. Tool calls and results are
collapsed into <details> blocks. Long tool results are truncated.

Avoids the Prettier template corruption that has broken the existing
HTML /export-session command (openclaw#41862, openclaw#22595, openclaw#49957) by using plain
text formatting with no template placeholders.

Also fixes pre-existing oxfmt issue in docs/automation/standing-orders.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Handle legacy assistant string content in extractAssistantText (Codex P1)
- Escape triple backticks in tool result/call code blocks (Codex P2)
- Use runtime.log instead of console.log (Greptile P2)
- Use os.homedir() instead of process.env.HOME for tilde expansion (Greptile P2)
- Include tool name in tool call summary tags (Greptile P2)
- Improve truncation test assertion directness (Greptile P2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mvanhorn mvanhorn force-pushed the feat/session-export-md branch from c623ed2 to c2c916b Compare March 20, 2026 16:13
@openclaw-barnacle openclaw-barnacle Bot added size: L and removed docs Improvements or additions to documentation size: M labels Mar 20, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2c916b16d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +186 to +188
const storePath = resolveDefaultSessionStorePath(agentId);
const sessionStore = loadSessionStore(storePath);
const entry = sessionStore[sessionKey];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve export store from configured session targets

sessionExportCommand hardcodes resolveDefaultSessionStorePath(agentId) and then reads sessionStore[sessionKey], which bypasses the config-driven store resolution used by other session commands. In setups with a non-main default agent or a custom session.store path/template, openclaw sessions export will read the wrong sessions.json and incorrectly fail with “Session not found” for valid sessions.

Useful? React with 👍 / 👎.

@mvanhorn
Copy link
Copy Markdown
Contributor Author

mvanhorn commented Apr 4, 2026

Closing — CI failures and no reviewer interest. Happy to resubmit if there's demand.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes commands Command implementations size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant