Notion Tasks Operations = Tasks DB + Block Operations
SELF-HEALING SKILL: If error occurs or new pattern discovered → update skill, increment version, log in
SKILL_RELEASE_LOG.md
Core Principle: Manage Improvado Notion tasks (create/query/update) and page blocks (append/insert) via NotionAgent CLI and Python client. Tasks DB only - not for reading arbitrary pages.
Notion Operations Architecture (Continuant - TD):
graph TD
Skill[Notion Tasks Operations] --> Tasks[Tasks Database]
Skill --> Blocks[Block Operations]
Skill --> Users[User Management]
Tasks --> Create[Create Task]
Tasks --> Query[Query Tasks]
Tasks --> Update[Update Status]
Blocks --> Append[Append to End]
Blocks --> After[Insert After Block]
Blocks --> Mermaid[Mermaid Diagrams]
Users --> Find[Find User]
Users --> List[List Users]
Task & Block Workflow (Occurrent - LR):
graph LR
A[User Request] --> B{Task or Block?}
B -->|Task| C[NotionAgent CLI]
B -->|Block| D[NotionClient API]
C --> E[Execute CRUD]
D --> F{Position?}
F -->|End| G[append_to_page]
F -->|After Block| H[append_blocks_after]
E --> I[Return URL]
G --> I
H --> I
Ontological Rule: TD for system structure, LR for operation workflow
Primary source: data_sources/notion/notion_client.py, data_sources/notion/notion_cli.py
Original session: 2025-01-15 - v1.0.0 Initial
Latest update: 2026-02-13 by Mikhail Molchanov - v1.5.0 Customer relation support
Release log: See SKILL_RELEASE_LOG.md
CRITICAL: Customer Field (MANDATORY for task creation)
BEFORE calling create_task, you MUST follow this checklist:
- ASK the user: "Which customer is this task for?" — even if the title seems obvious
- Look up the customer in Customers DB using
find_customer_by_name(name) - Pass
customer=parameter tocreate_task()with the exact name the user provided - If the user says "no customer" / "internal task" → skip, but ALWAYS ask first
WHY this is mandatory: Missing Customer field breaks downstream analytics — call taxonomy linking, hierarchy management, and customer reporting all depend on this relation. This was a recurring problem reported by the team.
How it works under the hood:
- Customer is a relation field in Tasks DB → Customers DB (
43a432c058404db8b70aa8974aa984fc) create_task(title="...", customer="T-Mobile")automatically looks up "T-Mobile" in Customers DB and sets the relation- Search is two-step: exact title match first, then contains fallback
Example flow:
User: "Create task: MDG Filter alerts by LOB"
Agent: "Which customer is this task for?"
User: "T-Mobile"
Agent: → create_task(title="MDG Filter alerts by LOB", customer="T-Mobile", ...)
When to Use
¶1 USE this skill:
- Create/update/query Notion tasks (Tasks DB ID:
42bffb6bf5354b828750be69024d374e) - Find users, manage task hierarchies
- Append blocks to pages (Mermaid, callouts, headings)
- Insert blocks after specific block (using URL anchor)
¶2 DO NOT use:
- Reading arbitrary Notion pages → use
NotionClient.get_block_children()directly - Extracting content from documents → use
data_sources/notion/notion_client.py
Block Operations (v1.2.0+)
¶1 Append to End:
from data_sources.notion.notion_client import NotionClient
client = NotionClient()
client.append_to_page(page_id, blocks)
¶2 Insert After Specific Block (CRITICAL):
Use after parameter in API. Get block_id from URL anchor:
URL: https://notion.so/page-id#2bf9aec6212580fab178ff9f9872e0f9
└── anchor = block_id (32 chars)
Convert to API format:
anchor = "2bf9aec6212580fab178ff9f9872e0f9"
block_id = f"{anchor[:8]}-{anchor[8:12]}-{anchor[12:16]}-{anchor[16:20]}-{anchor[20:]}"
# Result: "2bf9aec6-2125-80fa-b178-ff9f9872e0f9"
Insert after block:
import requests, os
def append_blocks_after(page_id: str, after_block_id: str, blocks: list) -> dict:
headers = {
"Authorization": f"Bearer {os.getenv('NOTION_TOKEN')}",
"Content-Type": "application/json",
"Notion-Version": "2022-06-28"
}
url = f"https://api.notion.com/v1/blocks/{page_id}/children"
data = {"children": blocks, "after": after_block_id} # KEY: 'after' param
response = requests.patch(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
¶3 Mermaid Diagram Block:
block = {
"type": "code",
"code": {
"language": "mermaid",
"rich_text": [{"type": "text", "text": {"content": "graph LR\n A-->B"}}],
"caption": [{"type": "text", "text": {"content": "Pipeline Flow"}}]
}
}
Notion defaults to Code view. User must toggle to Preview/Split manually.
¶4 Callout with Link:
block = {
"type": "callout",
"callout": {
"rich_text": [
{"type": "text", "text": {"content": "Source: "}},
{"type": "text", "text": {"content": "GitHub", "link": {"url": "https://github.com/..."}}}
],
"icon": {"type": "emoji", "emoji": "💻"},
"color": "gray_background"
}
}
Table Extraction (v1.4.0)
¶1 Fast CLI extraction:
python scripts/01_extract_table.py "https://notion.so/page#block_id"
python scripts/01_extract_table.py "https://notion.so/page#block_id" --format json
python scripts/01_extract_table.py "https://notion.so/page#block_id" --format csv --quiet
¶2 Formats: markdown (default), json, csv, raw
¶3 How it works:
- Extracts page_id and block_id from URL
- If anchor points to toggle/column → searches inside for table
- Falls back to page-wide table search
- Outputs clean formatted data
¶4 Python usage:
from data_sources.notion.notion_client import NotionClient
client = NotionClient()
rows = client.get_block_children(table_id)
for row in rows:
cells = row.get('table_row', {}).get('cells', [])
values = [''.join([t.get('plain_text', '') for t in cell]) for cell in cells]
Task Operations
¶1 Create Task (with Customer):
python -m data_sources.notion.notion_cli create-task "Title" --assignee daniel_kravtsov --priority High --customer "T-Mobile"
from data_sources.notion.notion_cli import NotionAgent
agent = NotionAgent()
task = agent.create_task(
title="MDG Filter alerts by LOB",
assignee="roman_vinogradov",
status="In Progress",
priority="High",
customer="T-Mobile" # ALWAYS set when task is customer-related
)
¶2 Find Customer by Name:
from data_sources.notion.agent_commands.task_commands import find_customer_by_name
customer_id = find_customer_by_name("T-Mobile") # Returns page ID or None
¶3 Query Tasks:
tasks = agent.get_tasks(assignee="daniel_kravtsov", status="In Progress", days_back=7)
¶4 Update Status:
agent.update_task_status(page_id="abc123", new_status="Done")
¶5 Find User:
user_id = agent.get_user_id("daniel@improvado.io") # Works with name, email, or UUID
Environment & Constants
NOTION_TOKEN="secret_..." # Required
NOTION_YOUR_EMAIL="your@email.com" # For auto-assignment
Tasks DB: 42bffb6bf5354b828750be69024d374e
Customers DB: 43a432c058404db8b70aa8974aa984fc
Daniel ID: NOTION_USER_ID
Nataliia ID: NOTION_USER_ID_2
Common Patterns
¶1 Clickable Links: Use plain URL or markdown [text](url). No bold/emoji prefix.
¶2 Partial Updates: Don't replace entire description. Target specific block.
¶3 Session Tracking: Add comment with session ID:
client.add_comment_to_page(page_id, f"Session: {session_id}")
Tests
Run tests before/after changes:
python .claude/skills/notion-tasks-operations/tests/01_test_notion_operations.py
Covered:
- Block ID extraction from URL anchors
append_blocks_after()with mocked API- Mermaid/Callout/Heading block creation
Related Files
data_sources/notion/notion_client.py- Core API clientdata_sources/notion/notion_cli.py- NotionAgent CLIdata_sources/notion/markdown_to_blocks.py- MD → Notion blocksdata_sources/notion/agent_commands/task_commands.py- Task CRUD with customer support
SKILL EVOLUTION: Error or new capability? → Fix it, update
SKILL_RELEASE_LOG.md, bump version.Ground Truth: Session
2026-02-13- v1.5.0 Customer relation support