Cross-platform CLI that auto-replies to new comments on your own LinkedIn posts and, optionally, sends a follow-up DM to the commenter. Ships as a linkedin-autoreply command with a guided init wizard.
LinkedIn's User Agreement prohibits automated access and interactions with the platform. This tool operates in a gray zone — it acts only on your own posts, at low volume, from your own device and IP. Risk is not zero.
By running this tool you accept that LinkedIn may restrict, suspend, or permanently ban your account. Use at your own risk.
| Platform | Status |
|---|---|
| macOS (Apple Silicon + Intel) | ✅ Developed & live-tested |
| Windows 10/11 | |
| Linux | linkedin-autoreply run from cron/systemd yourself. |
- Python 3.11 or newer
- Active LinkedIn account (you'll log in once, session cookies are stored locally)
- ~500 MB free disk space (Chromium browser for
scrapling+ virtualenv)
curl -fsSL https://raw.githubusercontent.com/JSap0914/linkedin-automation/main/install.sh | bashiex (iwr -useb https://raw.githubusercontent.com/JSap0914/linkedin-automation/main/install.ps1).ContentWhat the installer does:
- clones to
~/.linkedin-automation - creates
.venv - runs
pip install -e ".[dev]" - runs
scrapling install - creates a global wrapper command (
linkedin-autoreply) that always runs from the install root - immediately launches
linkedin-autoreply init
If the install directory already exists as a clean git checkout, the installer fast-forwards it. If it has uncommitted changes, it aborts rather than overwriting them.
Any Python 3.11+ works. You do not need to "activate" the venv — just call its binaries directly. This matches what install.sh / install.ps1 do internally and sidesteps PowerShell's Activate.ps1 pitfalls entirely.
git clone https://github.com/JSap0914/linkedin-automation.git
cd linkedin-automation
python3 -m venv .venv
.venv/bin/python -m pip install -e ".[dev]"
.venv/bin/scrapling install
.venv/bin/linkedin-autoreply initgit clone https://github.com/JSap0914/linkedin-automation.git
cd linkedin-automation
py -3 -m venv .venv
.venv\Scripts\python.exe -m pip install -e ".[dev]"
.venv\Scripts\scrapling.exe install
.venv\Scripts\linkedin-autoreply.exe initIf you really want to activate the venv in PowerShell, you must prefix the path with
.\and allow local scripts:Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned .\.venv\Scripts\Activate.ps1Without
.\, PowerShell treats.venvas a module name and fails withCouldNotAutoLoadModule.
Everything is pre-configured with sensible defaults. You only answer 2 questions — the rest is automatic.
- ToS acknowledgement — accept the automation risk (must type
y) - Prerequisite check — (automatic) Python version +
scraplingimportable - LinkedIn login — opens a Chromium window; log in manually (you have 5 minutes); session cookies are cached in
.profile/ - Enable auto-DM?
[Y/n]— sends a follow-up DM to commenters (1st-degree only, max 30/day) - Write config — (automatic) saves
replies.yamlwith defaults - Bootstrap — (automatic) marks all existing comments as "seen" so the bot won't reply to old ones
- Install scheduler — (automatic) registers a launchd agent (macOS) or Task Scheduler task (Windows) that runs the bot every 60 seconds. On Linux, prints a notice to set up cron/systemd manually.
- Done — prints commands for customizing settings
| Setting | Default | Change later with |
|---|---|---|
| Auto-reply enabled | true |
config set enabled false |
| Reply templates | 3 Korean thank-you messages | config wizard or config edit |
| Reply delay | 0 s (instant) | config set reply_delay_seconds_min 30 |
| Polling interval | 60 s | config set polling_min_interval_seconds 300 |
| Post lookback | 30 days | config set post_lookback_days 7 |
| DM: 1st-degree only | true |
config set dm.only_first_degree_connections false |
| DM: auto-accept invites | true |
config set dm.auto_accept_pending_invitations false |
| DM: max per day | 30 | config set dm.max_per_day 50 |
| DM: delay | 0 s | config set dm.delay_seconds_min 60 |
To re-configure interactively, run linkedin-autoreply config wizard anytime.
| Path | What it is | Safe to delete? |
|---|---|---|
replies.yaml |
Your configuration | No (regenerate via config reset) |
.profile/ |
Chromium profile + cookies (~100 MB) | No (you'll need to re-login) |
.cache/own_urn |
Cached LinkedIn person URN | Yes (auto-regenerated) |
seen_comments.db |
SQLite log of replied comments | Yes (but you may re-reply to old comments) |
logs/bot.log |
Rotating bot log | Yes |
~/Library/LaunchAgents/com.user.linkedin-autoreply.plist (macOS) |
Scheduler entry | Yes (run uninstall instead) |
Task Scheduler LinkedInAutoReply (Windows) |
Scheduler entry | Yes (run uninstall instead) |
linkedin-autoreply init # full setup wizard
linkedin-autoreply run [--dry-run] [--bootstrap]
linkedin-autoreply setup # re-login only (no config changes)
linkedin-autoreply update [--dry-run] [--skip-tests]
linkedin-autoreply config show
linkedin-autoreply config set <dotted.path> <value>
linkedin-autoreply config edit # opens in $EDITOR
linkedin-autoreply config wizard # re-run config section only
linkedin-autoreply config reset # overwrite with defaults
linkedin-autoreply config migrate # fill missing fields after schema change
linkedin-autoreply start # install + enable scheduler
linkedin-autoreply stop # disable (keep entry)
linkedin-autoreply uninstall # remove scheduler entry
linkedin-autoreply status # scheduler state + recent logs
linkedin-autoreply logs [-n N] # tail bot.log
linkedin-autoreply config set dm.enabled true
linkedin-autoreply config set dm.max_per_day 50
linkedin-autoreply config set enabled false # kill switch
linkedin-autoreply config set reply_delay_seconds_min 30
linkedin-autoreply config set reply_delay_seconds_max 120
linkedin-autoreply config set polling_min_interval_seconds 300 # 5 minRun these in order after init:
# 1. Config is valid
linkedin-autoreply config show
# 2. Scheduler is registered
linkedin-autoreply status
# → should show "Installed: yes" + "Enabled: yes"
# 3. Bot can actually call LinkedIn (no write, safe)
linkedin-autoreply run --dry-run
# → logs should show "Fetched N comments" and "Dry run complete"
# 4. Check logs
linkedin-autoreply logs -n 20
# 5. (macOS) verify launchd picked it up
launchctl list | grep linkedin-autoreply
# → should print one line
# 6. (Windows PowerShell) verify Task Scheduler
schtasks /Query /TN LinkedInAutoReplylinkedin-autoreply updateIf you installed via install.sh / install.ps1, this works from any current working directory because the global wrapper command always cds into the install root before invoking the CLI.
This:
- Aborts if your working tree is dirty (run
git statusto see) git pull --ff-only origin main(refuses non-fast-forward)- Re-runs
pip install -e ".[dev]"ifpyproject.tomlchanged - Runs
config migrateifbot/config.pyorbot/config_defaults.pychanged (fills new fields with defaults, drops removed fields — your values are preserved) - Reinstalls the scheduler if
bot/scheduler/templates/changed - Runs the pytest smoke suite (
--skip-teststo skip)
Flags:
--dry-run— inspect only, no pull/install/migrate--skip-tests— don't run pytest after update
Stops the bot without uninstalling:
linkedin-autoreply config set enabled falseThe next scheduled run exits cleanly with no LinkedIn API calls. Re-enable with ... set enabled true.
linkedin-autoreply uninstall # removes scheduler entry
deactivate # exit venv
rm -rf .venv .profile .cache seen_comments.db logs replies.yaml
cd ..
rm -rf linkedin-automation # manual-install pathIf you used the one-line installer, remove the installer-managed directory instead:
linkedin-autoreply uninstall
rm -rf ~/.linkedin-automation
rm -f ~/.local/bin/linkedin-autoreplyWindows (PowerShell):
linkedin-autoreply uninstall
Remove-Item -Recurse -Force $HOME\.linkedin-automation
Remove-Item -Force "$HOME\AppData\Local\Microsoft\WindowsApps\linkedin-autoreply.cmd" -ErrorAction SilentlyContinue| Before | After |
|---|---|
python setup.py |
linkedin-autoreply setup (setup.py is gone) |
bash launchd/install.sh |
linkedin-autoreply start (bash script is a shim) |
bash launchd/uninstall.sh |
linkedin-autoreply uninstall (shim) |
edit replies.yaml by hand |
linkedin-autoreply config set … or config edit |
| manual re-bootstrap | linkedin-autoreply config reset then run --bootstrap |
python bot.py [--dry-run] [--bootstrap] still works as a compatibility shim.
scrapling install --force # re-download ChromiumIf behind a corporate proxy, set HTTPS_PROXY first.
Session cookie expired (LinkedIn rotates them every ~1 year, or sooner if you log in elsewhere):
linkedin-autoreply setupBrowser window didn't open? Check that you have a display server. Over SSH you need -X forwarding or a local terminal.
A previous run crashed. Safe to delete:
rm logs/bot.lock logs/bot.lock.lock 2>/dev/nullYou skipped bootstrap. Run:
linkedin-autoreply run --bootstrapThis marks every current comment as "seen" without replying.
Call the binary inside the venv directly — no activation needed.
macOS / Linux:
.venv/bin/linkedin-autoreply --help
.venv/bin/python -m pip install -e ".[dev]" # if deps missingWindows (PowerShell or cmd):
.venv\Scripts\linkedin-autoreply.exe --help
.venv\Scripts\python.exe -m pip install -e ".[dev]"You ran .venv\Scripts\Activate.ps1 without the leading .\. PowerShell treats .venv as a module name instead of a relative path. Two fixes:
Easiest — skip activation entirely (recommended; matches what our installer does):
.venv\Scripts\python.exe -m pip install -e ".[dev]"
.venv\Scripts\scrapling.exe install
.venv\Scripts\linkedin-autoreply.exe initIf you insist on activating, prefix with .\ and allow local scripts:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned
.\.venv\Scripts\Activate.ps1-Scope Process only lasts for the current terminal session.
Your execution policy is Restricted (Windows default). Either:
# Temporary — only this terminal session
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned
# Permanent for your user — no admin needed
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSignedOr just use the venv binaries directly (no activation, no policy change needed).
The installer prefers python (then python3, explicit versions, then py -3.X). Microsoft Store python stubs under WindowsApps\ are deliberately skipped because they open the Store instead of running Python.
"No suitable Python runtime found" is emitted by py.exe itself when its registry (HKCU/HKLM\SOFTWARE\Python\PythonCore) doesn't list the requested version — even if you actually have Python installed. Common causes:
- You installed via the Microsoft Store —
py.exedoesn't see Store installs by default. - You installed from python.org but unchecked "Install py launcher" or "Add Python to PATH".
Diagnose what py sees:
py -0
$env:PYLAUNCH_DEBUG = 1; py -3.13 -c "import sys"Fix (one of):
- Re-run the python.org installer with both "Add python.exe to PATH" and "Install py launcher" checked.
- Or just run the install script with
pythondirectly instead of relying onpy:git clone https://github.com/JSap0914/linkedin-automation.git $HOME\.linkedin-automation cd $HOME\.linkedin-automation python -m venv .venv .venv\Scripts\python.exe -m pip install -e ".[dev]" .venv\Scripts\scrapling.exe install .venv\Scripts\linkedin-autoreply.exe init
On macOS/Linux, check what you have:
command -v python3.11 python3.12 python3.13 python3Install a recent CPython from python.org if none show up.
pip install -e ".[dev]" # reinstall depslaunchctl list | grep linkedin-autoreply
# If present but "Status: 78" or similar error code:
launchctl unload ~/Library/LaunchAgents/com.user.linkedin-autoreply.plist
linkedin-autoreply start # reinstallsschtasks /Create requires the user to have "Log on as a batch job" right. Some corporate Windows images disable this. Workaround: run linkedin-autoreply run manually from an elevated PowerShell, or skip the start step and invoke run via your own scheduler.
Uses LinkedIn's internal Voyager API (/voyager/api/voyagerSocialDashNormComments for replies, /voyager/api/voyagerMessagingDashMessengerMessages for DMs) — the same API the web app calls. Authenticates via browser session cookies stored in .profile/ (not username/password).
Every 60 seconds the OS scheduler runs one poll cycle:
- Load session cookies from
.profile/ - Fetch your posts from the last N days (default 30)
- For each post, fetch comments
- Filter out: your own comments, nested replies, already-seen comments (tracked in
seen_comments.db) - For each new comment:
- Pick a reply template (per-post binding > keyword match > random default)
- Personalize with
{name}님(Korean honorific) using the commenter's first name - Wait
reply_delay_seconds(0 = instant) - POST the reply
- If
dm.enabled+ commenter passesonly_first_degree_connectionscheck: auto-accept any pending invitation from them, wait for connection, then send a DM - Record success in
seen_comments.db
All pre-existing comments at the time of linkedin-autoreply init (bootstrap step) or run --bootstrap are marked seen without reply. New comments landing after that moment are what the bot responds to.
See Scrapling for the underlying Patchright-based browser automation.
- Single LinkedIn account per install — multi-account would need separate clones
- No retry on Voyager 429 — the bot logs + exits; next scheduled run picks up where it left off
- No rich text replies — plain text only (LinkedIn supports mentions but this bot doesn't emit them)
- Korean-first templates — defaults are in Korean (
{name}님 ...); override via wizard orconfig set - Windows live validation pending — see Platform Support above
bot/
├── cli.py # Typer CLI entry point
├── cli_commands/ # One file per subcommand
├── onboarding/ # init wizard steps
├── scheduler/ # launchd / Task Scheduler abstraction
│ └── templates/
├── config.py # pydantic schema
├── config_defaults.py # single source of truth for defaults
├── config_io.py # YAML read/write + dotted-path setter
├── config_migrate.py # schema drift detection + safe merge
├── updater.py # git pull + pip install + drift detection
├── orchestrator.py # main poll cycle
├── auth.py # browser login + cookie extraction
├── voyager.py # Voyager API client (scrapling)
├── comments.py, posts.py, replies.py # endpoint wrappers
├── messaging.py, connections.py, invitations.py # DM + connection logic
├── templates.py, personalization.py # template matching + {name} substitution
├── db.py # SQLite seen_comments / dm_sent
├── lockfile.py # cross-platform singleton lock (filelock)
├── killswitch.py
└── logging_config.py
tests/ # 255 unit tests (pytest)
launchd/ # macOS launchd shim scripts (legacy path)
pyproject.toml # deps, entry points, pyright config
Found a bug, especially on Windows? Open an issue. Include:
- OS + Python version
- Output of
linkedin-autoreply status - Last 50 lines of
logs/bot.log - What you ran + what you expected
MIT