I spent two weeks building a Notion agent. Then I realised I was solving the wrong problem — one global Claude Code skill did it better.
May 5, 2026

Managing professional side projects at the scale Claude helps you build them is mentally charging. At some point I was actively working on 20+ projects simultaneously — Ai-blog, FlowTimer, MindfulTennis, Cricket Arena, a handful of smaller tools I will probably never finish (we have all been there) — and the overhead of tracking all of them started to feel like a second job.
I needed one unified view. Notion felt like the right answer for me. If you are curious about why I chose Notion over Linear or a plain markdown file, drop a comment below — that is worth its own post.
The immediate problem was: how do I keep Notion in sync without it becoming another chore? My first instinct was to build an agent. A scheduled trigger, running every few hours, reading my git history and updating task statuses automatically. I spent about two weeks on this before I realised I was solving the wrong problem.
I was still doing all the thinking myself — summarising what I built, deciding what "done" meant, approving the agent's proposals. I was just running more machinery to arrive at the same result. The fix was not a smarter agent. It was a Claude skill.
An agent knows nothing about your session. It reads files, inspects git history, and makes educated guesses about what you were doing and why. Useful when you are not there. Wasteful when you are.
A skill runs inside your conversation. It already knows you fixed the auth bug because you just told Claude about it. It knows you renamed that function because Claude helped you rename it. The session context is not just convenient — it is irreplaceable. No amount of git log parsing reconstructs the "I tried approach A, it failed, switched to B, and here is why" that lives in a live conversation.
Rule of thumb: if a human needs to be present for it to make sense, it is a skill. If it should run at 3am without you, it is an agent. Status updates to Notion require the first kind of presence — the "what I actually decided and why" is in your head, not in the diff.
Not sure which one your next automation idea needs? Try this:
Will you be present and working when this task runs?
As in — actively in a Claude Code session while it happens
I put the Notion skill in ~/.claude/skills/ — the global directory that Claude Code picks up regardless of which project you are working in. One file, available across all 20+ projects. No copying, no syncing, no project-level setup beyond a one-time mapping step per project.
The skill file looks roughly like this:
# ~/.claude/skills/notion-sync.md
Reads recent git activity and session context, fetches the current Notion project and tasks,
proposes updates, and executes on confirmation.
## Steps
1. Get current project: `basename $(pwd)`
2. Load project_map.json from ~/.claude/projects/
3. Fetch current task board from Notion (MCP)
4. Summarise session changes
5. Propose updates → confirm → execute
That basename $(pwd) on step 1 is where it gets interesting. When Claude reads the skill instructions and reaches that line, it runs the command through its Bash tool — takes about a second — and the project name lands in the conversation before anything else happens. The skill wakes up knowing exactly which project it is in without you having to say a word.
The same pattern loads the project config:
cat ~/.claude/projects/$(basename $(pwd))/project_map.json
Claude executes this, reads the output, and the project map is already resolved before any Notion call is made. Zero extra back-and-forth just to figure out which page to update.
MCP tools speak in Notion page IDs. Your folders speak in names. Something has to translate Ai-blog into 1a2b3c4d-5e6f-....
project_map.json is that translation layer. You set it once per project — run /notion-map-project, it finds or creates the right Notion page and writes the ID locally. Done. Never touch it again.
{
"Ai-blog": "1a2b3c4d-5e6f-7890-abcd-ef1234567890",
"FlowTimer": "9z8y7x6w-5v4u-3t2s-1r0q-pabcdefghijk",
"MindfulTennis": "abc12345-def6-7890-ghij-klmnopqrstuv"
}
Notion page IDs are stable forever (I have one project that has been in Notion for two years and the ID has never changed). Set it once, the file just works. The mental model: project_map.json is not a database. It is a name badge — worn once, recognised everywhere.
Once you have the project name and the page ID, two tools are in play: Bash and MCP. Their jobs do not overlap.
| What you are doing | Tool | |---|---| | Reading git history | Bash | | Reading local config | Bash | | Listing current Notion tasks | MCP | | Updating a task status | MCP | | Summarising session changes | Claude (in-conversation) | | Writing the summary to Notion | MCP |
Never cross the streams. Git questions go to Bash, Notion writes go to MCP. (I tried routing everything through MCP once, for tidiness. It is slower and you end up fighting the tool for simple things like reading the current branch name.)
The four commands each own exactly one moment:
/notion-map-project — runs once per project. Finds or creates the Notion page, writes the ID to project_map.json. After that, never touch it again.
/notion-start — beginning of a session. Pulls your task board. You pick what you are working on today. Takes about 30 seconds and means your Notion board actually reflects reality (which, if you have ever stared at a "Doing" column full of months-old stale tasks, you know the value of).
/notion-task done "task name" — mid-session, one line. No ceremony, no context-switching. You finished a thing, you record it, you move on.
/notion-sync — end of session. Git log plus session context feeds into a proposed Notion update. You review, you press y, Notion is synced. The proposal is usually accurate because it is working from what actually happened in the conversation, not just file diffs.
The mistake I made early on: building four separate commands and trying to remember which one to run. So I added a /notion router — one command that detects context and routes itself automatically. Start of session, it pulls the task board. End of session, it reads git activity and proposes updates. Mid-session, it surfaces the quick-action menu. Same command every time, right thing every time. (This is the difference between a tool and a workflow. Tools make you choose. Workflows route you.)
One more piece: the Stop hook. It fires when Claude finishes responding. Mine prints:
Tip: run /notion to sync your progress to Notion
One line of bash. Costs nothing. Runs automatically at the end of every response. Means I never walk away from a session without the option to sync staring me in the face.
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "echo 'Tip: run /notion to sync your progress to Notion'"
}
]
}
]
}
}
You can make it smarter — check if the project is mapped before printing, suppress it for trivial sessions. But the plain version is already useful. You do not need more automation. You need a reminder at the right moment.
The heavy lifting — git introspection, MCP calls, diff proposals — happens inside the skill. My only job is pressing y. That is a realistic ask across 20+ active projects. Manually opening Notion, finding the right page, updating five task statuses, and writing a session summary for each project is not.
The /notion skill has not fixed my discipline (I am still the person who opens a doc called "daily notes" with good intentions and then ignores it for two weeks). It has just made the right thing slightly easier than the wrong thing, which turns out to be enough.
If you set this up and run into something unexpected, drop a comment below — I have probably broken it the same way at some point.