Skip to content

feat: add --cookies-file flag for session persistence#2125

Open
mvanhorn wants to merge 3 commits intolightpanda-io:mainfrom
mvanhorn:feat/cookies-file
Open

feat: add --cookies-file flag for session persistence#2125
mvanhorn wants to merge 3 commits intolightpanda-io:mainfrom
mvanhorn:feat/cookies-file

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

Summary

Adds a --cookies-file CLI option that loads cookies from a JSON file at startup and saves them back on exit. Works with fetch, serve, and mcp commands.

Why this matters

AI agents that need to maintain login sessions across multiple Lightpanda invocations are currently blocked. Issue #335 (7 comments) asks for cookie pass/dump. Steel Browser and Browserbase both offer session persistence, which is table-stakes for browser automation.

The cookie JSON format matches CDP Network.Cookie, so cookies exported from Puppeteer's page.cookies() or Playwright's context.cookies() work directly.

Demo

Demo

Usage:

lightpanda fetch --cookies-file session.json --dump markdown https://example.com

First run saves cookies to session.json. Subsequent runs load them, sending cookies with requests. Updated cookies are saved back on exit.

Changes

src/Config.zig: Added cookies_file field to Common options, accessor cookiesFile(), CLI parsing for --cookies-file, and help text.

src/cookies.zig (new): Cookie file I/O module with loadFromFile and saveToFile. Parses/writes JSON arrays of {name, value, domain, path, expires, secure, httpOnly} objects. Uses std.json for parsing and manual JSON writing for output. Handles missing files gracefully (first run creates the file on exit).

src/lightpanda.zig: Calls cookies.loadFromFile after session creation and defer calls cookies.saveToFile before session cleanup.

Cookie file format

[
  {"name": "session_id", "value": "abc123", "domain": ".example.com",
   "path": "/", "expires": 1744329600, "secure": true, "httpOnly": true}
]

Fixes #335

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

mvanhorn and others added 2 commits April 9, 2026 19:41
Add a --cookies-file CLI option that loads cookies from a JSON file
at startup and saves them back on exit. This enables AI agents to
maintain login sessions across multiple Lightpanda invocations.

The cookie format matches CDP Network.Cookie (compatible with
Puppeteer's page.cookies() export):
  [{"name":"sid","value":"abc","domain":".example.com","path":"/",
    "expires":1234567890,"secure":true,"httpOnly":true}]

Closes lightpanda-io#335

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
std.io.bufferedWriter doesn't exist in Zig 0.15.2. Use the
file.writer(&buf) pattern that matches the rest of the codebase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@krichprollsch
Copy link
Copy Markdown
Member

Thanks for the contribution.
This is an important feature, but I have some questions and comments.

  1. Curl uses 2 distinct options to load and save cookies: --cookie is a read-only option to load cookies, and --cookie-jar is a write-only option to save cookies. If you want both, you can use the same file for both options. I think it's good to follow the same convention.

  2. Curl uses the Netscape file format to save cookies. However, this format has been abandoned by browsers and does not support modern fields like secure and http-only, which are important when running JS. So I don't want to use this format, but I'm wondering if other formats exist that we could be compatible with.
    Once introduced, a file format can be hard to change while keeping backward compatibility.
    EDIT: Claude thinks the cookie JSON format you implemented is compatible enough.

  3. My biggest concern is about CDP server and multiple connection support. You can create multiple connections through CDP, and each connection gets its own browser instance. How should we handle the cookie store in this case? Should the last browser instance replace the cookie storage? Should we create one file per session?

  4. You say "Works with fetch, serve, and mcp commands." but as far as I can see, the current implementation only loads and saves cookies for the fetch command — is that correct?

@krichprollsch krichprollsch self-requested a review April 10, 2026 13:48
- Escape braces in help text format string to avoid std.debug.print
  interpreting them as format specifiers
- Use writer.interface for std.Io.Writer methods (writeAll, stringify)
  instead of calling them directly on fs.File.Writer
- Replace writer.flush() with writer.end() per codebase convention

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mvanhorn
Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review!

Fixed the build errors in cc6ffb5 — unescaped braces in the help text format string and wrong writer API usage (fs.File.Writer vs std.Io.Writer interface). CI should go green now.

On your points:

1. --cookie / --cookie-jar split: Agree, following curl's convention makes sense. I'll split it into --cookie (load-only) and --cookie-jar (save-only), where using the same file for both gives the current behavior.

2. JSON format: Good to know you're OK with the CDP-compatible JSON format. I'll keep it as-is.

3. CDP multi-session: This is the key design question. Currently each Session owns its own cookie_jar. For the CDP/serve case with multiple connections, I see a few options:

  • Shared jar, last-write-wins: All sessions read from the file at start, each session saves on close. Simple but lossy if sessions overlap.
  • Per-session files: e.g. cookies-{session_id}.json. Avoids conflicts but creates file sprawl.
  • Load once, save once: Load cookies into the Browser (or a shared store) when the server starts, save when it shuts down. Sessions share the jar in-memory.

I'd lean toward option 3 for serve/CDP since the cookie jar is really "this browser's state" rather than per-tab state. What do you think?

4. Scope: You're right, the current implementation only hooks into fetch(). The cookiesFile() accessor already handles all three modes in Config, but the actual load/save logic is only in lightpanda.zig:fetch(). I'll extend it to serve and mcp once we align on the multi-session approach (point 3).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pass in cookies / dump out cookies?

2 participants