Skip to content

feat(cli): Complete Local session IDs and labels - #1781

Merged
BYK merged 5 commits into
mainfrom
feat/cli-local-session-completion
Nov 1, 2026
Merged

BYK merged 5 commits into
mainfrom
feat/cli-local-session-completion

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #1759. local session view|close|reset|retention, local envelope list|view, and local event list|view now complete the session: IDs of the running daemon's sessions, described by label and state (web (open)), then labels that only one session has (the commands accept those too).

Where the sessions come from. Sessions exist only in the daemon's memory, and sentry __complete makes no network calls. So the daemon now keeps a small local-sessions.json next to its discovery state. It is written atomically with owner-only permissions, the same way as local-control-plane.json. The daemon rewrites it when a session is created, closed, or expires, and removes it on shutdown. It holds only id, label, state, and expiresAt. The serializer picks those fields explicitly, so capabilities, cwd, and telemetry never reach the file. The completer:

  • ignores the file once the PID that wrote it has exited
  • leaves out retained sessions past their expiry and any malformed entries
  • loads its reader with a dynamic import, so the eager completion path is unchanged

A stale entry can only suggest a session that the command then reports as not found.

I chose this over calling the daemon's /sessions endpoint from the completer. That would put a loopback request on every TAB press, add a timeout to tune, and make the completer read the control token. The snapshot keeps the fast path file-only. The cost is one small file write per session lifecycle change. Activity updates don't trigger a write, because every ingested envelope records activity.

What's not covered. The second word of local envelope view <session> <envelope> / local event view <session> <event> still completes nothing. Envelope and event IDs exist only in the daemon's retention buffers. Mirroring them would mean a disk write on every ingested envelope, plus decoding for events, on the daemon's hot path. Labels with whitespace are also left out of the suggestions, because they don't stay one shell word.

Open question: should the snapshot also carry cwd, so the description can tell apart same-named sessions from different directories? I left it out to keep the file minimal.

via @BYK.

--

View Session in Sentry

sentry-junior Bot and others added 2 commits November 1, 2026 16:36
The Local session, envelope, and event commands took their session as free
text. Sessions live only in the daemon's memory and `sentry __complete`
makes no network requests, so the daemon now mirrors each session's ID,
label, state, and expiry to an owner-only `local-sessions.json` next to its
discovery state, rewritten when a session is created, closed, or expires
and removed on shutdown. Completion reads it, ignores it once the daemon's
PID has exited, and suggests session IDs (described by label and state)
and the labels only one session has.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Nov 1, 2026 4:55pm UTC

Request Review

@github-actions github-actions Bot added the risk: medium PR risk score: medium label Nov 1, 2026
Comment on lines +524 to +527
let snapshotWrites = Promise.resolve();
const snapshotSessions = (sessions: readonly LocalSession[]) => {
snapshotWrites = snapshotWrites
.then(() => writeLocalSessionSnapshot(directory, process.pid, sessions))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are we not using new Promise(..) pattern like normal people? 😀 (and don't tell me it's because you're a bot!)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair 😄 There's no promise to construct here: the chain only existed to keep async writes in order. In c898089 the snapshot is written synchronously instead (writeFileSync + renameSync, still atomic and 0600). The file is tiny and the daemon only writes it when a session is created, closed, or expires, so each write finishes before the next one starts and there's nothing left to serialize or await on shutdown.

"id" | "label" | "state" | "expiresAt"
>;

function snapshotPath(directory: string): string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't we just use Sentry CLI data directory for this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It already lives there: both callers passed getConfigDir(), the directory that holds cli.db and local-control-plane.json. In c898089 the module resolves getConfigDir() itself, so the directory parameter is gone and the location can't drift. If you meant storing the sessions in cli.db (like completion_cache) rather than a file, I kept it a file because the daemon would otherwise need SQLite writes alongside every other CLI process. Happy to switch if you prefer that.

Comment on lines +116 to +117
[...sessions.values()]
.map(toPublicSession)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this is one of those rare cases where Array.from() might be better as it already accepts a map function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in c898089: Array.from(sessions.values(), toPublicSession).sort(…).

Comment thread packages/cli/src/lib/complete.ts Outdated
}
return [...completions]
.filter(([value]) => value.startsWith(partial))
.map(([value, description]) => ({ value, description }));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Like my earlier comment, I wonder if doing Array.from().filter() would work better? Though in this case it swaps the .filter and .map order.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in c898089: Array.from(completions, ([value, description]) => ({ value, description })).filter(({ value }) => value.startsWith(partial)). Mapping first only builds a few extra small objects for this list size.

@BYK
BYK requested a review from MathurAditya724 November 1, 2026 16:45
Address review: the snapshot resolves the CLI config directory itself
instead of taking it as a parameter, and writes synchronously, so no
promise chain is needed to keep writes in order. Use `Array.from` with a
map function where it replaces a spread and `.map`.
};
await mkdir(directory, { recursive: true, mode: 0o700 });
const path = snapshotPath(directory);
mkdirSync(getConfigDir(), { recursive: true, mode: 0o700 });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe we should also have an ensureConfigDir() which creates the dir and returns its path now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call: db/index.ts already had a private ensureConfigDir(). In ca00c9a it is exported and returns the directory, and it replaces the hand-rolled getConfigDir() + mkdir(…, 0o700) in the session snapshot writer, ensureLocalDaemon, and runLocalDaemonForeground.

Address review: export `ensureConfigDir()` from `db/index.ts`, returning
the directory it creates, and use it where the Local daemon and session
snapshot created the config directory by hand.
@BYK
BYK enabled auto-merge (squash) November 1, 2026 17:04
@BYK
BYK merged commit 2bd57c1 into main Nov 1, 2026
25 checks passed
@BYK
BYK deleted the feat/cli-local-session-completion branch November 1, 2026 17:05

This branch was previously deployed

1 inactive deployment
Preview — ca00c9a9 Deployed Nov 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant