Medium syndication agent
The next Lab Notes post becomes a Medium story that points back at the original.
A post on a small company blog is read by the people who already found the blog. Medium has readers, search placement and distribution of its own, and a copy there with a canonical link back to the original earns that reach without competing with the original in search. Done by hand it means re-pasting every post, fixing the formatting Medium breaks, and setting a canonical link Medium no longer offers a switch for.
Running on our own company since Oct 11, 2026 · Medium · Risk: Low. Everything on this page is public; there is nothing to sign in for.
Tasks and outputs
What it takes on
Pick the post
Lists the Lab Notes posts and the opted-in posts, drops the retired, the opted-out and the already syndicated, and takes the oldest. One per run.
Import, replace, publish
Imports the post’s URL for the canonical link, pastes the site’s own rendering over Medium’s, fills the preview title, subtitle and topics, publishes.
Verify and record
Reads the canonical back from the live story and writes the story into the registry; the push that follows deploys the site.
Who it is for: Teams who already keep a blog and want selected posts in front of Medium’s readers, while the original page stays the one search engines and AI engines treat as the source.
How we use it: We have run it on our own blog since October 2026, two mornings a week (Monday and Thursday) from the founder’s machine. It cross-posts the Lab Notes posts only: the site is already read as an AI news blog, and the Medium profile is meant to be about AI visibility, so the daily news posts are deliberately left out. The first story went out on October 11 with its canonical verified; every story since is on the public run log.
What goes to Medium, and how
01Lab Notes posts go. A post of any category goes with publishToMedium: true; publishToMedium: false keeps it out. Daily AI-news posts are left out on purpose.
02Retired posts and posts already in the registry are skipped. Oldest first, one per run.
03The canonical link comes from Medium’s own importer. The script reads it back from the live story and records whether it is the original.
04The body is the site’s rendering: GFM markdown, tables as lists, site-relative links made absolute.
05First line: “Originally published at claw-world.app on <date>”. Last line: the same with the full URL.
06Preview title is the post title, preview subtitle the excerpt cut to Medium’s 140 characters, five topics. The script never retries a publish.
The rules the agent’s output is written to, from the file named above. This site prints no example output; what the agent has shipped is in its run log.
Workflow
How a run goes, step by step
Pick a step to see what goes in, what comes out, and where a person is involved.
Step 1 of 6
Pick the post
Reads every post’s frontmatter, keeps the Lab Notes posts and the ones opted in with publishToMedium, drops the retired and opted-out ones and whatever is already in the registry, and takes the oldest. If the queue is empty, the run stops before opening Chrome and says so.
web/content/blog/*.md and the registry
One slug
Nobody, unless the queue is empty: then someone writes a Lab Notes post or opts one in.
Step 2 of 6
Import for the canonical
Opens Medium’s Import a story in the signed-in Chrome and gives it the post’s URL. Medium fetches the page and creates a draft that carries the canonical link and the original date; its formatting is not kept.
The post’s public URL, server-rendered
A Medium draft with a canonical link
When Chrome is unreachable or Medium is not signed in: the run stops with exit code 2 and a person signs in.
Step 3 of 6
Replace the body
Renders the markdown itself (GFM, tables as lists, absolute links, the lead line and the footer), copies it as rich text and pastes it over the imported body. Then checks that the first block is the lead line, that the footer is there and that Medium’s own import footer is gone; if not, the run stops.
The post file
The draft with the site’s rendering
When the check fails: a person deletes the draft (--delete) and runs again with the Chrome window left alone.
Step 4 of 6
Fill the dialog and publish
Sets the preview title, the preview subtitle from the excerpt and the five topics, then clicks Publish. In a dry run it stops before the click and leaves the tab open for a person to read.
Title, excerpt and topics
A live story
In a dry run, a person reads the draft before the real run. A renamed Medium button stops the run here with a screenshot.
Step 5 of 6
Verify the canonical
Opens the live story in the same Chrome and reads its canonical link. The registry records verified when it is the blog URL and unverified otherwise; the story is live either way.
The story URL
verified or unverified
When unverified: a person opens the story, checks its head, and runs --verify.
Step 6 of 6
Record and deploy
Writes the story URL, id, date, time, canonical state and topics into the registry under the post’s slug. The scheduled run then commits and pushes that one file and triggers the deploy, which builds this log from the registry.
The run’s result
One entry in web/content/medium-syndication.json
When the push fails: a person pushes. The entry is already written locally.
Before you start
What you’ll need to bring
Company material
Nothing beyond the blog itself: the lead line, the footer and the canonical all point at your own site.
Source material
A blog with server-rendered post pages and frontmatter the script can read: a category, a title, an excerpt and a date. Ours is the Lab Notes category on this site.
Platform account
A Medium account signed in once in a dedicated Chrome profile. No API token (Medium issues none), no password handed over.
Run environment
A machine that can keep that Chrome open during runs, Node.js for the publishing script, and an agent runner that follows a written workflow. We use Claude Code.
Run log
Recent runs, as they happened
The full log is public and read-only. It holds every run on record, including the ones that stopped to wait for a person.
View the full run logGuides and files
Set it up yourself
The setup guide, and the files the agent is built from. You run it in your own environment, with your own accounts.
- 01
Runtime
- Node.js 20 or later, with the dependencies installed in the scripts’ folder (a markdown renderer, a frontmatter parser and the browser driver).
- A scheduler that starts the wrapper two mornings a week, or an agent runner that follows a written workflow — ours is a scheduled task on the founder’s machine; the steps are deterministic, so no model is in the loop.
- 02
Browser setup
- A Chrome started with remote debugging on a fixed port (we use 9222) and a dedicated profile, signed in to Medium once by a person. The LinkedIn, Reddit and Bing agents share the same window.
- The script opens its own tabs and closes them; it never touches another tab. Keep the keyboard off that window while a run is pasting: keystrokes typed while it has focus land in the editor.
- 03
Account setup
- A Medium account, signed in in that Chrome. No API token (Medium stopped issuing them) and the script never sees the password.
- A blog whose post pages are server-rendered. Medium’s importer fetches the page itself, so a page that is empty without JavaScript imports as nothing.
- 04
Configuration
- The rule for which posts go: a list of categories (ours is Lab Notes) plus the per-post opt-in and opt-out field in the frontmatter.
- The five default topics, and the site address the canonical, the lead line and the footer point at.
- The registry file the publish step writes, in the repository the site builds from, so the run log can be generated from it.
- 05
First run
- Run the script on one post without --publish: it imports, replaces the body and fills the publish dialog, then leaves the tab open. Read the draft on Medium, then delete it with --delete.
- Publish one post for real, open the live story and check its canonical link by hand once; the script records what it read back.
- Commit the registry and confirm the run appears on the log after the deploy.
syndicate.mjs
Script · Node. The queue, the import, the render and paste, the publish dialog, the canonical check and the registry write.
inspect.mjs
Script · Node. Prints a Medium page’s text and controls through the same Chrome, for when the editor changes.
run-scheduled.ps1
Scheduler · PowerShell. The Monday and Thursday run: takes the next post, publishes, commits and pushes the registry, triggers the deploy.
medium-syndication.json
Registry · JSON. One entry per story published, keyed by post slug. The file this log is built from.
Not packaged for public download yet. The guide on the left describes what each one does, step by step, so you can rebuild the setup; the Hosted and Fully managed tiers run them for you today.
Rather have us run it?
Hosted: your accounts in a cloud browser we run, and you review what goes out. Fully managed: we set the strategy and run the agents.
Good to know
Where it stops and asks for you
- Medium is not signed in, or Chrome is unreachable. Exit code 2 before anything is imported. A person opens Medium in that Chrome and signs in.
- The first body block is not the lead line. Something else landed in the editor, usually keystrokes while the window had focus. The run stops, the draft stays for a person to delete, nothing is published.
- The canonical cannot be read back. The story is live; the record is marked unverified and waits for a person to check the head and run --verify.
- The queue is empty. The run stops before opening Chrome and says so.
- Medium changed its editor. A locator stops matching and the run stops with a screenshot. The inspector script prints the page’s controls so the right one can be picked.
Risk notice
What to watch for
- Medium’s terms govern automation on the platform. The script works through a browser a person signed in, publishes at most two stories a week, and never retries a publish.
- A canonical copy earns no search ranking of its own; that stays with the original, which is the point. What it earns is reach inside Medium, and only Medium’s own stats show that.
- A rewrite mode, with original text on Medium instead of a copy, is deliberately not built until measurement shows the Medium copies being cited by AI engines.
FAQ
Questions about this agent
The script exits with code 2.
Chrome is not reachable on its debugging port, or Medium is not signed in in that Chrome. Start the debug Chrome, open medium.com in it and sign in by hand; nothing else changes.
The run hangs before it opens anything.
An idle new-tab page left in the debug Chrome has been seen to stop the DevTools connection from answering. The script closes those first; if the attach still hangs, close the tab through the debugging port’s /json/close/<id> endpoint and run again.
The run failed saying the first body block is not the lead line.
Something else landed in the editor, usually because the Chrome window had focus while the script was pasting. The run stops on purpose rather than publish a body it did not write; delete the draft and run again with the window left alone.
The import came back with an old version of the post.
Medium caches a URL it has fetched before and returns that copy on a second import. The body is replaced with the current rendering anyway, so the text is right; the import only has to supply the canonical link.
Would you rather we ran this agent for you? There is a managed service for that.
Talk to us