·

Real World Workflow Keeping Notion And Code In Sync

Walk through a real production workflow that uses Notion MCP so your AI agent can read and write pages and databases end to end.

Every workflow in the previous five topics was a piece. This one assembles them into something a real team can actually run sprint over sprint: Notion as the spec of record, an AI coding agent as the bridge that keeps status current and code aligned, and a repeatable close-out step that publishes release notes back without anyone manually re-typing what already happened in git.

This isn't a hypothetical. It's the shape of a workflow that holds up because each step has a narrow, verifiable scope — nothing here asks the agent to make a judgment call with no way to check it.


Workflow Overview: Spec-Driven Development with Notion as Source of Truth

The core idea: a Notion database (call it "Engineering Tasks") holds one row per unit of work, with properties for Status (status type: Not Started / In Progress / In Review / Done), Spec (a relation to a detailed spec page, or the row's own page body if the task is small enough not to need one), PR (a URL property), and Acceptance Criteria (as to_do blocks in the page body, not a property — checklists don't fit cleanly into a property type).

// Simplified schema for the "Engineering Tasks" database
{
  "properties": {
    "Name": { "title": {} },
    "Status": {
      "status": {
        "options": [
          { "name": "Not Started" },
          { "name": "In Progress" },
          { "name": "In Review" },
          { "name": "Done" }
        ]
      }
    },
    "PR": { "url": {} },
    "Sprint": { "select": { "options": [{ "name": "Sprint 42" }] } },
    "Owner": { "people": {} }
  }
}

The workflow has three recurring steps, each owned by a distinct moment in the development cycle: reading the spec to plan work (start of task), updating status and criteria as work proceeds (during development), and publishing what shipped back to Notion (at merge/release). The point of splitting it this way isn't process for its own sake — it's that each step has a different verification story. A generated implementation plan gets checked against the spec by a human before coding starts. A status update gets checked by the fact that it's a single, narrow property change. A release note gets checked against the actual merged diff. None of the three steps asks the agent to be right about something nobody will verify.

High-level prompt pattern used at the start of each task:

"Task JIRA-4821 / Notion row 'Saved Searches — Rate Limiting'. Read the linked
spec, propose an implementation plan, then wait for my go-ahead before writing
any code or touching Notion."

That "wait for my go-ahead" instruction is the connective tissue across the whole workflow — every write-back step in this topic assumes a human checkpoint happened first. This isn't a fully autonomous pipeline, and it shouldn't be; the value is in eliminating the busywork around the sync, not in removing the person who decides whether work is actually done.

Tips
- Model checklists as to_do blocks in the page body, not as a database property — Notion doesn't have a native "checklist" property type, and forcing acceptance criteria into a text property loses the ability to check individual items off.
- Keep a human checkpoint between "plan generated" and "code written" even in a workflow this mature — the failure mode of a bad autonomous plan compounds fast once code and Notion updates are both downstream of it.
- Treat the Spec relation as optional for small tasks — not every row needs a full page; a task with 2-3 lines of context in its own body is fine and shouldn't be forced into a separate linked page.


Step 1: Reading the Notion Spec and Generating an Implementation Plan

claude "Read the Notion page linked from the 'Spec' property on the row
'Saved Searches — Rate Limiting' in the Engineering Tasks database. Then:

1. Summarize the requirement in 2-3 sentences
2. List the acceptance criteria exactly as written (they're to_do blocks)
3. Propose an implementation plan: which files change, what's net-new, any
   open design questions you'd want answered before starting

Do not write any code or touch Notion yet — just show me the plan."

Tool sequence: API-post-database-query (filtered to the row by title), API-retrieve-a-page on the row to get the Spec relation's target page ID, then API-retrieve-a-block (recursively, for children) on the spec page. The plan itself is generated entirely from that context — no write tools involved in this step, which is exactly why it's safe to let the agent run this part with less supervision than the write-heavy steps that follow.

A detail worth building into this prompt as a standing habit: asking for open design questions the plan surfaces, not just the plan itself. Specs — even good ones — routinely under-specify an edge case, and having the agent name what it's uncertain about (rather than silently picking an interpretation) is what keeps this step from quietly encoding a wrong assumption into the code that follows.

Example output from this step:

Plan: Implement token-bucket rate limiting on POST /api/saved-searches.

Files:
- src/middleware/rateLimiter.ts (new)
- src/routes/savedSearches.ts (modify — apply middleware)
- src/config/limits.ts (modify — add SAVED_SEARCH_RATE_LIMIT constant)

Open questions:
- Spec says "limit search creation" but doesn't specify the rate limit window
  (per minute? per hour?) — the acceptance criteria only test the ceiling
  (20 total), not a rate. Confirm before implementing bucket refill logic.
- No mention of what error response shape to return on rate-limit rejection —
  check if there's an existing convention elsewhere in the codebase.

This output is genuinely useful specifically because it stopped and flagged ambiguity rather than guessing — that's the plan doing its job, not a gap in it.

Tips
- Always ask explicitly for open design questions as part of the plan — it's the cheapest way to catch spec ambiguity before it becomes a wrong implementation.
- Keep this step read-only (no code, no Notion writes) so it's safe to run with lighter supervision than the steps that follow.
- If the agent's plan doesn't mention any open questions on a non-trivial spec, be suspicious — it more likely means it didn't look closely enough, not that the spec was flawless.


Step 2: Updating Status Fields and Acceptance Criteria as Work Progresses

Once the plan is approved and coding starts, the sync step is deliberately small: flip Status to In Progress at the start, check off acceptance criteria as they're satisfied, and move to In Review when a PR opens. Each of these is a single, narrow write — which is exactly what keeps this step reliable enough to trust with less oversight than plan generation.

"Set Status on the 'Saved Searches — Rate Limiting' row to 'In Progress'."
{
  "properties": {
    "Status": { "status": { "name": "In Progress" } }
  }
}

Checking off criteria mid-implementation, tied to actual test passes rather than vibes:

"I just got the '20 saved searches max' test passing. Go to the spec page
and check off the matching acceptance criterion to_do block. Don't touch any
other criteria — only the one about the 20-item limit."

Constraining the scope explicitly ("only the one about...") matters here more than it might seem to — without it, an agent asked to "update the criteria" after one test pass has been observed to over-eagerly check off adjacent items that look related but haven't actually been verified. A checked to_do block in Notion is a claim someone else on the team will trust; it should only get checked because something concrete backs it up, not because it's plausible.

At PR-open time:

"PR #847 is open for this task: https://github.com/org/repo/pull/847.
Update the row: set PR property to that URL, set Status to 'In Review'."
{
  "properties": {
    "PR": { "url": "https://github.com/org/repo/pull/847" },
    "Status": { "status": { "name": "In Review" } }
  }
}

Wiring this to an actual git event rather than a manual prompt is the natural next step for teams that want less manual triggering — a post-PR-open webhook or CI step that shells out to claude -p "..." with the same instruction, non-interactively, closes that gap. The prompt itself doesn't need to change; only how it gets invoked does.

claude -p "Update the Notion row for task \$TASK_ID: set PR property to
\$PR_URL, set Status to 'In Review'." --allowedTools "mcp__notion__*"

Restricting --allowedTools to just the Notion MCP tools in a non-interactive CI context is worth doing explicitly — it keeps an automated invocation from having access to anything broader than the one job it's meant to do.

Tips
- Scope acceptance-criteria check-offs to exactly the criterion just verified — never let a single test pass trigger a broader "update all related criteria" sweep.
- Tie status transitions to real events (PR opened, PR merged, CI green) rather than an agent's own assessment of "looks done" — the property should reflect something externally checkable.
- When automating this step in CI, restrict the agent's tool access to only the Notion MCP tools it needs for that specific update — least privilege applies to agent invocations just like it does to service accounts.


Step 3: Auto-Publishing Release Notes and Docs Back to Notion

The close-out step: once a PR merges, generate a release note or changelog entry from the actual diff and commit history, and publish it to Notion — not from memory of what was planned, but from what actually shipped.

"PR #847 just merged. Read the diff and commit messages. Then:

1. Set Status on the 'Saved Searches — Rate Limiting' row to 'Done'
2. Check off any remaining unchecked acceptance criteria that the diff
   confirms are satisfied — verify against the actual code, don't assume
   from the plan
3. Create a new page under the 'Changelog' database:
   - Title: derived from the PR title
   - Release Date: today
   - Content: a 2-3 sentence summary of what changed, written for a product
     audience (not implementation detail), plus a linked reference back to
     the task row and the PR"

The instruction to verify criteria "against the actual code, don't assume from the plan" is doing important work — the plan from Step 1 describes intent, and intent and shipped code diverge often enough (a descoped edge case, a different error-handling approach) that treating the plan as ground truth here would quietly encode inaccuracies into what's supposed to be the definitive record of what happened.

// Changelog database row properties
{
  "properties": {
    "Name": { "title": [{ "text": { "content": "Saved search creation is now rate-limited" } }] },
    "Release Date": { "date": { "start": "2026-08-21" } },
    "Related Task": { "relation": [{ "id": "task-row-page-id" }] }
  }
}

Writing the changelog summary "for a product audience, not implementation detail" is a deliberate instruction, not filler — engineers left to their own devices tend to write changelog entries as terse implementation summaries ("added token bucket middleware to POST endpoint") that mean nothing to anyone outside the team. Asking explicitly for the audience shift produces something that's actually useful to whoever reads the Changelog database next — support, PM, or a future engineer trying to understand why a limit exists.

For teams running this at real cadence, batching a weekly rollup is worth adding as a fourth, lower-frequency step:

"Query the Changelog database for entries with Release Date in the last 7
days. Summarize them into a single 'Weekly Engineering Update' page, grouped
by area (API, Frontend, Infra), and share the page link in our #eng-updates
context (paste it back to me, I'll post it manually)."

Keeping the actual posting manual here — "paste it back to me" rather than wiring a direct Slack post — is a deliberate, small piece of caution: publishing internally-facing engineering detail is low-risk to get slightly wrong, but a fully automated pipeline with zero review step, run enough times, eventually publishes something embarrassing or premature. The manual paste costs ten seconds and removes that risk entirely.

Tips
- Verify acceptance criteria against the actual merged diff at close-out, not against the original plan — plans and shipped code diverge more often than a spec-driven workflow likes to admit.
- Write changelog and release-note content explicitly for a non-engineering audience — the raw implementation summary an engineer would default to isn't useful to anyone reading the Changelog later.
- Keep at least one manual step (a final "post this" action) in any part of the pipeline that publishes outward-facing or team-wide visible content — full automation there is rarely worth the risk it removes so little effort to avoid.


Tips

The workflow that actually holds up in production isn't the most automated one — it's the one where every write-back step has a narrow, independently verifiable scope: a status flip tied to a real event, a checklist item tied to a passing test, a changelog entry checked against a real diff. Notion MCP makes each of those steps fast; it doesn't make any of them trustworthy on its own, and treating it that way is where spec-driven workflows quietly go wrong.

Tips
- Keep human checkpoints at plan-approval and at anything publishing outward — automate the narrow, verifiable steps in between freely.
- Build the habit of verifying against ground truth (real diffs, real test results, real PR state) rather than against the agent's own prior output at each write-back step.
- Revisit this workflow's prompts every few months as your Notion schema and team conventions evolve — a workflow this specific to your database's property names and options needs the same maintenance as any other piece of internal tooling.