Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Claude Chokidar

A watchdog for your Claude account's Active Sessions. It checks the session list on a schedule and terminates any session that is not from a location you have protected — while making it very hard to ever terminate one you care about.

It runs on your locally installed Chrome and signs in by reusing your existing claude.ai session cookie, so it does not create a second login or add another device to your session list.


Contents


Protected locations

The rule is decided by the country Claude shows at the end of the location:

  • Outside India: always kept. Any session whose location ends in a country other than India (NO, US, GB, ...) is never terminated. So is any location that shows no country at all.
  • Inside India (..., IN): only these cities are kept. Every other Indian city (Pune, Nashik, Latur, Delhi, ...) is terminated.

The Indian cities are hard-coded in index.js and main.py (PROTECTED_LOCATIONS). The --allow flag can only add cities, never remove one:

Location Matches, for example
✅ anywhere outside India Bergen, Vestland, NO, London, England, GB
✅ Mumbai Mumbai, Maharashtra, IN
✅ Navi Mumbai Navi Mumbai, Maharashtra, IN
✅ Panvel Panvel, Maharashtra, IN
✅ Thane Thane, Maharashtra, IN
✅ The current session the browser the bot itself is using
❌ any other city in India Pune, Maharashtra, IN, Nashik, Maharashtra, IN

India is detected only when the last comma-separated part is exactly IN or India and a city comes before it, so Indianapolis, Indiana, US is not mistaken for India. City matching is case-insensitive and substring-based.

To protect an extra Indian city without touching the code:

node index.js --allow "pune,nashik"

Safety rules

Beyond the location list, five rules make a wrong termination hard:

  1. Only the Active sessions table is ever read. The account page also lists Trusted devices (Device | Added) just above it. Read blindly, a trusted device's Added date would be parsed as its location, making it look like an unprotected session. The bot locates the table under the Active sessions heading, maps columns by their header names (so an added column such as Updated changes nothing), and scans nothing else. If that table cannot be identified, the round is skipped entirely.
  2. Unknown location = keep. If the location cell is blank or unreadable (slow render, layout change), the row is kept, never terminated.
  3. Re-check before acting. The row is re-read from the live DOM immediately before the terminate click. If it now reads differently, or now classifies as protected, the bot skips it and re-scans.
  4. Runaway guard. If every row looks unprotected — impossible on a healthy account, since the current session is always protected — the bot assumes it misread the page and terminates nothing that round.
  5. "Log out" can never be clicked. The account page carries a plain Log out button (and on some accounts Log out of all devices), which would end sessions this bot exists to protect. Two independent layers stop it: a capture-phase listener injected into the page swallows any click on a logout control before the app sees it, and every click the bot makes is checked against the element's own label first. Terminating a single session goes through the row menu and its Terminate session confirmation, neither of which says "log out".

A termination is only counted once the row has actually disappeared from the table.


Requirements

  • Node.js 18+
  • Google Chrome installed locally (the bot drives chrome.exe; it does not need Playwright's bundled Chromium)
  • A Claude account you are signed in to in that Chrome

Setup

1. Install

git clone git@github.com:Amrit-Nigam/claude-chokidar.git
cd claude-chokidar
npm install

2. Get your session cookie

The bot authenticates as you by reusing your existing claude.ai session cookie. To copy it:

  1. Open https://claude.ai in the Chrome where you are already signed in.
  2. Press F12 to open DevTools.
  3. Go to the Application tab.
  4. In the left sidebar: Storage → Cookies → https://claude.ai.
  5. Find the row named exactly sessionKey.
  6. Click it and copy the Value — it starts with sk-ant-sid02- and is around 130 characters long.

Which cookie exactly? You will see several similar names. Use this mapping:

Cookie name in Chrome .env key Needed?
sessionKey SESSION_KEY Yes — this is the one
sessionKeyV3 SESSION_KEYV3 Optional
sessionKeyLC SESSION_KEYLC Optional
sessionKeyV3LC SESSION_KEYV3LC Optional

SESSION_KEY on its own is enough — that is the configuration this project is tested with. Add the optional ones only if sign-in ever fails without them.

If the cookie list shows the same name twice (claude.ai keeps one host-only and one domain-wide copy), put the second value in a _v2 key, for example SESSION_KEYV3_v2.

Copy the Value only — not the cookie name, not quotes, not a trailing semicolon.

3. Create your .env

cp .env.example .env

Then edit .env:

SESSION_KEY=sk-ant-sid02-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

.env is gitignored. Treat it like a password — anyone holding that value can use your Claude account.

On Windows, do not create it with echo ... > .env in PowerShell. PowerShell writes UTF-16, which corrupts the value. Use an editor, or run:

npm run session-key -- "sk-ant-sid02-..."

which writes a correctly encoded session-key.txt instead.

4. Check it works

Run one scan in preview mode. Nothing is ever terminated in --dry-run:

npm run dry-run

Expected output:

Protected (always): outside India, and in India: mumbai, navi mumbai, panvel, thane
Mode              : DRY-RUN (Preview only)

Using your local Chrome: C:\Program Files\Google\Chrome\Application\chrome.exe
Reusing your existing claude.ai session (sessionKey) - no new sign-in.
Safety guard armed: every "Log out" control is blocked in this window.

[17:13:55] Check #1: Inspecting active sessions...
  [KEEP]  Chrome (Mac OS X)        | Location: Mumbai, Maharashtra, IN | Protected location
  [KEEP]  Chrome (Windows) Current | Location: Mumbai, Maharashtra, IN | Current session
  [KEEP]  Claude (iOS)             | Location: Bergen, Vestland, NO    | Outside India (or country not shown)
  [WOULD TERMINATE] Chrome (Linux) | Location: Pune, Maharashtra, IN | Created: ...
[17:13:55] Check complete. Total: 4 | Kept: 3 | Flagged: 1

Check that the [KEEP] lines list everything you expect to survive, then run it for real.


Running the bot

npm start

That starts the watchdog and scans every 60 seconds until you stop it with Ctrl + C.

Other one-off modes:

npm run once         # scan once, terminate, exit
npm run dry-run      # scan once, terminate nothing, exit
npm run bot:dry-run  # keep scanning, terminate nothing (monitoring mode)

Scan intervals

Pick a ready-made interval:

Interval Command Headless (no window)
Every 30 seconds npm run bot:30s npm run bot:30s:headless
Every 1 minute (default) npm run bot:1m or npm start npm run bot:1m:headless
Every 2 minutes npm run bot:2m npm run bot:2m:headless
Every 3 minutes npm run bot:3m npm run bot:3m:headless
Every 4 minutes npm run bot:4m npm run bot:4m:headless
Every 5 minutes npm run bot:5m npm run bot:5m:headless
Every N minutes (1–60) npm run bot:<N>m npm run bot:<N>m:headless

Every whole minute from bot:1m to bot:60m exists, each with a :headless variant. Any other flag can be passed after --, e.g. npm run bot:7m -- --dry-run.

Or set any interval you like, in seconds or with a unit (s, m, h):

node index.js --interval 120     # every 2 minutes
node index.js --interval 90m     # every 90 minutes
node index.js --interval 2h --headless
node index.js --interval 3600    # every hour

Between scans the bot prints when the next check is due:

⏳ Next check in 300s (Press Ctrl+C to stop)...

A shorter interval reacts faster; a longer one is gentler on the account and your machine. Every 5 minutes is a good middle ground if 1 minute feels noisy.

Running in the background

Once your .env works, add --headless so no window is shown.

Windows (PowerShell)

# Start detached, logging to bot.log
Start-Process -WindowStyle Hidden -FilePath node `
  -ArgumentList "index.js","--interval","300","--headless" `
  -RedirectStandardOutput bot.log -RedirectStandardError bot.err

# Watch the log
Get-Content bot.log -Wait

To start it automatically at login, create a Task Scheduler task that runs node D:\path\to\claude-chokidar\index.js --interval 300 --headless.

macOS / Linux

nohup node index.js --interval 300 --headless > bot.log 2>&1 &
tail -f bot.log
pkill -f "node index.js"

With PM2 (any OS)

npm install -g pm2
pm2 start index.js --name claude-chokidar -- --interval 300 --headless
pm2 logs claude-chokidar
pm2 save && pm2 startup     # survive reboots

Always-on server with expiry alerts

On a Linux server, run the bot under notify-wrapper.js via systemd instead of pm2. The wrapper runs npm run bot:1m:headless, and when something goes wrong it pauses the bot and emails you:

Problem What the wrapper does
Session key expired / rejected retries once, then pauses + emails
Logged out mid-run ("Log out of all devices", revoked key) retries once, then pauses + emails
Crash loop (4 failures in 30 min) pauses + emails
Hung (no scan for 10 min) or 5 unreadable scans in a row restarts; counts toward the crash loop
systemd gives up restarting the wrapper claude-chokidar-alert.service emails

While paused, a reminder goes out every 24h. Saving a new key (npm run session-key -- <value>) resumes it automatically within ~15s and sends a "back online" email; npm run chokidar:resume clears any other pause.

Setup (Ubuntu, Node 20, Google Chrome installed):

cd ~/claude-chokidar && npm ci --omit=dev
# .env: SMTP_USER / SMTP_PASS (Gmail app password), NOTIFY_EMAIL, NOTIFY_ALSO - see .env.example
npm run session-key -- "<sessionKey value>"
npm run notify:test                       # confirm the email arrives
sudo cp deploy/*.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now claude-chokidar
journalctl -u claude-chokidar -f

All command-line options

Usage: node index.js [options]

  --interval, -i <time>   Time between scans: seconds, or 90s / 7m / 2h
                          (default: 60)
  --once                  Run a single scan and exit
  --dry-run               Preview only; never terminates anything
  --allow, -a <list>      Extra protected Indian cities, comma-separated.
                          ADDED to the always-protected list; the built-in
                          protected locations cannot be disabled.
  --session-key <value>   Session cookie to use (overrides .env)
  --chrome-profile <name> Local Chrome profile: folder, display name or email
  --chrome-path <exe>     Path to chrome.exe (default: auto-detected)
  --separate-profile      Never touch the real Chrome profile
  --cdp [port]            Attach to a Chrome already running with a debug port
  --user-data-dir <dir>   Where to persist the bot's browser data
                          (default: ./chrome_session)
  --headless              Run without a visible window
  --list-profiles         List local Chrome profiles and exit
  -h, --help              Show help

Examples:

# Every 15 minutes, headless, also protecting Pune
node index.js --interval 900 --headless --allow "pune"

# Use a different Chrome profile
node index.js --chrome-profile you@example.com

# See which profiles exist
npm run profiles

How sign-in works

The bot reads your session cookies in this order of precedence:

  1. --session-key "<value>" on the command line
  2. the CLAUDE_SESSION_KEY environment variable
  3. session-key.txt (written by npm run session-key -- "<value>")
  4. .env ← the normal setup

If none is configured, the bot opens a window and waits for you to sign in once; that login is then remembered in ./chrome_session. Note that signing in this way does add one extra session to your list.

Why it cannot just read your everyday Chrome profile. Chrome blocks this by design, and both doors are shut:

  • Chrome 136+ refuses remote debugging when running on the default user-data-dir, so Playwright cannot drive your normal Chrome as-is.
  • Chrome's app-bound cookie encryption (v20 cookies) means a copied profile decrypts to nothing — the copy is always signed out.

Reusing the cookie is the supported way through. If you would rather attach to a real browser, close Chrome completely and run npm run chrome:debug, then npm run connect.


Troubleshooting

"This browser profile is not signed in to Claude" Your SESSION_KEY is expired, truncated, or was written as UTF-16. Re-copy the sessionKey value from DevTools and update .env. Session cookies do expire — expect to refresh this occasionally.

Sign-in fails even with a fresh SESSION_KEY Add the optional SESSION_KEYV3 (and SESSION_KEYV3_v2 if the name appears twice) as described in step 2.

"Could not find 'Active sessions' table" The page did not finish rendering, or you are not signed in. The bot skips that round and retries on the next interval; nothing is terminated.

"Chrome is already running, so it cannot be switched into debug mode" Informational. The bot uses its own profile directory (./chrome_session) and carries on — your everyday Chrome is untouched and stays usable.

Nothing gets terminated Check the [KEEP] reason printed for each row. Location unknown - kept for safety means the location cell did not render; Protected location means it matched your protected list.

A session you want removed is in a protected city That is deliberate: protected locations cannot be terminated by this bot. Remove it from claude.ai's settings page yourself.


Python version

main.py mirrors index.js — same protected locations, same safety rules, same .env handling.

pip install -r requirements.txt
playwright install chromium

python main.py --dry-run --once      # preview
python main.py --interval 300        # every 5 minutes

The Node version is the one that is actively exercised; the Python port is kept in step but is not equally tested.


Files in this repo

File Purpose
index.js The bot: scanning, classification, termination, safety guards
main.py Python port of the same bot
chromeProfile.js Finds your local chrome.exe and Chrome profiles
save-session-key.js Writes session-key.txt with correct encoding
launch-chrome-debug.js Starts your own Chrome with a debug port
notify-wrapper.js Always-on supervisor: emails and pauses on expiry, logout, crashes
deploy/*.service systemd units for the supervisor and its failure alert
.env.example Template for your .env
chrome_session/ The bot's browser profile (gitignored)

.env, session-key.txt and chrome_session/ are all gitignored and must stay out of version control.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages