Chat TagsColour-code, categorise and annotate VS Code chat sessions. Thirty chats down that sidebar. Every single one grey. Every single one named after whatever half-arsed shit you typed at eleven at night before you knew what the job actually was. One of them is holding the thing you care about. The rest are dead. They look identical. Chat Tags gives them colours, categories, a second line of text, and somewhere to bury the corpses.
Why this is a separate view instead of just fixing the real oneBecause you can't fix the real one. The surface isn't fucking there, hacks and proposed APIs included. This got checked against the shipped workbench before a line was written:
The session model has Not an oversight. It just wasn't a thing anyone needed until you had thirty chats open and no bloody idea which was which. So this sits next to the real list, reads the same files off disk, and draws them the way you wanted them in the first place. It's a second opinion. The first one was shit. What the colours meanTwo signals. They never fight, because they're on different axes. That's basically the whole design.
The left border means exactly one thing: something happened in that session since you last opened it, and it has stopped happening. Attention rows also get max wash, a full-strength dot with a halo, and a heavier title. Nothing else in the view draws a left border, so it can never mean two things at once. Open the session and it clears. Mark all read turns up in the toolbar only while there's something to mark, because a button that does nothing is just decoration. First run stamps a baseline timestamp. Without it every session you've ever opened lights up as unread on install day, which is a hell of a way to say hello. Colour attaches to the category, not the chat. Inside a webview colours are plain CSS, which answers to nobody's palette rules. Twelve curated ones plus a custom hex field. Busy is not the same as wanting youA chat writes to its file the whole time an agent is working in it. Every one of those writes reads as "something happened here", so a session doing exactly what you asked used to sit there lit up like it needed rescuing — which is how you end up ignoring the rows that genuinely do. Three states, read off the file rather than off a timer:
A working session doesn't count towards Mark all read either. It isn't unread yet — it hasn't finished saying anything. Where the reading comes fromA turn opens when a request is appended to the session file and closes when its result is written. In between, Only the patch log tells the truth about it. The whole-session serialiser rewrites both Two things had to be handled, and both were found in real files rather than guessed at: A reopened session lies about old turns. Open a chat that's been sitting for months and it re-emits A turn left open is not the same as a turn in flight. Close a window mid-answer and the result that would have closed the turn never gets written, so it stays open for good. Fifteen sessions on this disk are sitting like that. The state only reads as live when the file has also moved inside One limit worth knowing: a chat that was already mid-answer when the window opened is read from the tail of its file, which covers the common case and not a turn whose opening request is megabytes back. It sorts itself out on the next message either way. Which sessions can run things without askingVS Code lets you set a permission level per chat. Most people set it once, forget which chats they set it on, and then wonder why one of them is merrily running commands. A row that isn't on the default level gets a small pill next to its title. That's it. Nothing shows on the default level, because a badge that appears on every row is wallpaper.
The pill reads the level out of the session file, so it costs nothing and it's right even for chats you set up months ago and haven't opened since. There's a second pill, Auto-approving, for the other way a chat ends up running commands unattended: the Allow All Commands in this Session button in a terminal prompt. That one is worth calling out because VS Code keeps it nowhere — it lives in memory, it vanishes when the window reloads, and nothing on disk says it was switched off. Chat Tags works it out by watching what the chat actually does: a command that ran without asking puts the pill up, and the next one that had to ask takes it down. If your machine has the auto-approve permission levels locked out by policy, that button is the only route left, and this pill is the only warning you get. One honest limit: it shows what the next message in that chat will run as, not what the old ones ran as. Change the level today and the pill changes today, even though yesterday's messages ran under the old one. What each chat is running onOff unless you ask for it — Show model and context, in the sort menu. Turn it on and each row picks up a small reading at the end of its subtitle: the model that chat is set to, a bar, and how full that model's context window is. Both numbers come out of the chat's own file. The name is whatever the model picker says, which means a chat set to Auto reads Auto — the model Auto actually reached for on a given turn is written deep inside the request payload, well past anything worth reading to draw a list. The percentage is the size of the last prompt sent, measured against the window that model advertises. It is one prompt, not a running total: it climbs through a turn as tool results pile onto it, and drops back when the conversation gets trimmed. Past three-quarters full it goes amber, past nine-tenths it goes red. That is the point where a long chat starts quietly dropping its own early history, which is worth seeing on the list rather than discovering in an answer. Plenty of chats will show a model and no percentage, and that's not a bug in the reading. Only some providers write a prompt size into the session file at all; where none was ever written, none is invented. The other blank case is a session large enough that its newest measurement sits past the cheap read — it fills in the moment that chat next says anything. Ordering and groupingTwo orders, in the sort menu:
Both newest first. And they are not the same bloody list — across 23 real sessions barely a third land in the same place under both, because a chat you started in July and picked up yesterday sits at opposite ends of them. Group by category puts a heading above each category's sessions, in whatever order the categories sit in, with everything unassigned dumped under Uncategorised at the bottom. Point a session at a category you deleted and it lands there too. Where else would it go. That order is yours to set. Each row in the Categories panel has a handle on the left: drag it, or focus it and use ↑ and ↓, and a line shows the gap the row is about to land in. The same order drives the list in every With the list in groups, a chat moves between them by being dragged there. Pick one up, and the group under the pointer lights up as the place it will land; let go and it belongs to that category. Uncategorised is a target like any other, so dragging out is how you take a category off a chat. The Archived block is not — archiving is its own thing and lives on the A row still opens the chat when you click it. A press only becomes a drag once the pointer has moved far enough that it cannot have been meant as a click, and the click that would otherwise follow the drop is thrown away — releasing a chat onto a heading should not also fold that heading away. Selecting more than oneCtrl-click (⌘-click) rows to tick them, shift-click for a run, or hit the tick that appears on any group heading to take the whole group — collapsed groups included, since the heading knows its chats whether or not their rows are drawn. The heading's box fills in solid when all of a group is ticked and shows a dash when only some of it is. Ticked rows go a shade of the theme's selection colour and their dot turns square. Shape rather than size, so nothing shifts sideways as rows go in and out of the set, and the row's own signals — the left border, the pills, the category wash — all still read through it. While anything is ticked, the toolbar is replaced by a bar saying how many, with the actions:
A mixed selection gets both buttons, each counting only the chats it would actually move. Neither appears when it would move nothing. And dragging any ticked row carries the whole set into the group you drop it on, rather than leaving the rest behind. A plain click still opens a chat, and doing that abandons the selection — the same bargain every list on your desktop makes. Archiving and deletingTwo completely different things. They work differently on purpose.
Archive is oursArchived sessions drop out of the list into a section at the bottom, shown only when Show archived is on. Until then a footer counts them — because a hidden session with no visible count is how you end up absolutely certain something ate your chat. VS Code has its own archived flag, and it is a separate store. Theirs is scoped to one workspace and lives in that workspace's Because nothing reconciles the two, a machine that had done its archiving in the chat view used to show every one of those chats sitting live in this pane. So the first time the pane reads a workspace it takes VS Code's archived flag as a starting point. It runs one way and once per chat: archive something here afterwards and it stays archived, restore it and it stays restored. Nothing is ever written back — the archive API doesn't exist for an extension, and the workbench's own route rejects a local chat outright. Archived sessions get skipped by the automatic subtitle sweep and don't count towards the unread badge. Something you've put away doesn't get to spend your money or ask for your attention. Delete goes through VS CodeNever by deleting the file. The session index lives in workspace storage, and the workbench's own delete returns early on an id it doesn't recognise. Rip the file out from under it and the index keeps an entry pointing at nothing, which is a worse mess than the one you started with. So delete hands the job to the workbench. It clears any open widget, removes the history entry, deletes both files it knows about, and rewrites the index. It runs its own confirmation dialog — names the session, tells you it can't be undone — so Chat Tags doesn't stack a second one on top of it like some kind of idiot. The command resolves whether you confirmed or cancelled, so the file on disk is the only honest evidence of what happened. That goes to the log, and Chat Tags only forgets the session's metadata once the file has actually gone. Generated subtitlesA language model can write the second line for you. It can write the title too. Three buttons on every row, and the same three sit in the Command Palette under Chat Tags. What comes back is a status line, not an essay. "Waiting for API key." "Retrying after request errors." That's it. That's the whole feature. Three things it can write
Status decays. Right for something you're still working. Task summary doesn't decay, so it's right for the one you'll come back to in a week and stare at like a stranger's grocery list. Every message the model sees goes through a boilerplate filter first, so When the end of a session is nothing but noise, the prompt carries no last request at all. A missing line beats a misleading one. Feed a model Overwriting something you wroteA generate button sitting over text you typed yourself is dimmer than the others, and it takes two clicks. The first arms it — lights up, stays visible whether or not you're hovering, tooltip flips to You have customised this subtitle already, confirm regenerate? The second one fires. It disarms itself after six seconds. Only hand-written text is guarded. The armed state lives in the view, not in the session record, so a repaint from the file watcher can't quietly disarm a decision you're halfway through making. Renaming a sessionChat Tags never writes to the session file. VS Code holds it open and appends to it. A record written underneath gets clobbered on the next flush, and a half-written line takes the whole session with it. Not worth it for a rename, or for anything else. So titles live in extension state and get drawn over the top, whether you wrote it or the model did. A dotted underline marks a title as ours rather than the session's own. Clear the field and it drops. The native Chat list and the editor tab both carry on showing the original. The tab's label comes from the chat model's own title, and Making a rename stick everywhereRename in VS Code…, on the row menu, opens the workbench's own rename box with the Chat Tags title already in it. One Enter and it goes through the same call the built-in Rename uses, which is what the editor tab, the native list and the session file all read from. The pane then notices its own copy has become redundant and drops it, dotted underline and all. Two honest limits. The box can't be skipped. The only other things that reach that call are a chat slash command registered with the chat registry rather than the command registry, and the agent-host title sync — neither reachable from out here. And VS Code writes the title into the session file only for a chat it currently has loaded. Rename one that isn't open and the native list and the next tab that opens it both update, while this pane carries on showing what it was showing. Which modelThe pane lists whatever your window actually offers and defaults to Automatic: no selector, then the cheapest family on the table — Pin one and its id goes in No models, or consent declined, and the explicit path says so once. The automatic path shuts up for the rest of the window rather than nagging you every sweep. What it costsOff by default. Every generation is a real billable request against whatever provider your window has. Real money. Yours.
The scope rule does the heavy lifting. Without it, flicking Manual always wins. Edit a generated subtitle and it's yours — A subtitle also remembers which kind it is. Generate a status line and it stays a status line, even where What leaves your machineA prompt is not the same as a tool call. Running the command is what you asked for. Handing whatever it touched to a model provider is a second thing entirely, and session files are absolutely full of shit that was never meant to travel. One real session had a live Vaultwarden token sitting in its tool activity and a password as its last request. Both would have gone out verbatim, to a third party, because a sidebar wanted to write four words about it. So credentials get masked on the way in:
Deliberately small. A stop on the obvious shapes, not a scanner, and it runs on the finished prompt so nothing added later slips past it. What it catches becomes This is not a claim that nothing else leaks. Prose you typed is sent as prose you typed. If a session is full of things you wouldn't paste into a stranger's window, leave Starting and opening sessionsThe Clicking a session can rearrange your window. Three modes, in the pane under When a session is clicked:
A new chat lands exactly where a clicked one would, so the A new chat joins the list when you send its first message. VS Code keeps an untouched chat in memory and writes nothing to disk, and this pane reads files off disk — so there is nothing to draw until you type. When the row does appear it takes the selection, so the list matches the chat you are looking at. Open something else while you are typing and your selection stays where you put it.
Sessions and chat in one paneThe built-in Chat view sets Drag the Chat view's title onto the Chat Tags container, or Command Palette → View: Move View → Chat → Chat Tags. What you can't do is render the chat conversation inside this extension's webview. Webviews are sandboxed iframes and workbench UI doesn't project into one. Sharing a container is the actual mechanism, and it's your move to make, not something the extension can declare on your behalf. SettingsEverything lives in the pane. The view title bar and the Settings UI get nothing. Buttons across the header, one panel open at a time:
Order and grouping get their own menu instead of a row of radio buttons behind the gear. You flip those several times a session. The stuff behind the gear you set once and forget about. Whichever panel is open, its button stays lit until you click it again. That's the only way to close it, so it has to read as pressed rather than merely hovered. The Titles and subtitles get edited through their pencils, or by double-clicking the title. Neither is a click target — the whole row opens the session, and an edit affordance covering half the row turned that into a coin flip. Clearing the title field restores the session's own.
Every one is a real VS Code setting, so the pane and the Settings UI are two windows onto one stored value rather than two stores having an argument about it. Edit either. Install
The Chat Tags icon shows up in the activity bar. Every activation writes where it found your sessions to ContributingBuild steps, the probes, the read model internals and every gotcha worth knowing are in CONTRIBUTING.md. LicenceGPL-3.0-or-later. See LICENSE. Copyright © 2026 WulffTech. Do what you like with it. Ship a modified version and the source goes with it — that is the whole deal. |
