brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
asana-apiIntegrates with Asana API to manage tasks, projects, workspaces, goals, portfolios, and webhooks using the official asana Python SDK for work management automation.
Independent GitHub snapshot · Automated classification · Original documentation governs
name: asana-api description: Integrates with Asana API to manage tasks, projects, workspaces, goals, portfolios, and webhooks using the official asana Python SDK for work management automation. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: coding triggers: asana api, asana python, asana tasks, asana projects, asana webhooks, asana sdk, asana automation archetypes:
Integrates with the Asana API to programmatically manage tasks, projects, sections, portfolios, goals, workspaces, tags, time tracking, and webhooks using the official asana Python SDK.
asana.Client.access_token() with a personal access token for simple auth, or asana.Client.oauth() for OAuth 2.0client.tasks, client.projects, client.sections, etc.opt_fields to control which fields are returned; Asana returns minimal fields by default?limit= and offset — the SDK provides .items generators for transparent iterationclient.webhooks.create() for push-based change notifications instead of pollingasana.error.AsanaError for typed error handling with status codesUse this skill when:
Generate an Access Token — Navigate to https://app.asana.com/0/developer-console and create a Personal Access Token. For multi-user apps, register an OAuth 2.0 app. Checkpoint: Verify the token with client.users.get_user("me").
Initialize the Client — client = asana.Client.access_token(token). Set client.options["client_name"] to identify your integration. Checkpoint: Call client.users.get_user("me") and print the name.
Choose Resources and Fields — Asana returns compact objects by default. Always specify opt_fields like opt_fields="name,completed_at,assignee.name,due_on" to include related data. Checkpoint: Verify the response dict includes the expected keys.
Paginate Through Collections — Use the auto-pagination generators: client.tasks.find_all(project=project_gid, iterator=True) yields tasks without manual offset tracking. Checkpoint: Confirm that calling next() on the iterator returns the expected item shape.
Perform CRUD Operations — Create tasks with client.tasks.create_in_workspace(), update with client.tasks.update(), comment with client.tasks.add_comment(). Each returns the affected object. Checkpoint: Re-fetch the task to verify the update persisted.
Handle Errors — Wrap in try/except asana.error.AsanaError. Check e.status for 401 (auth), 403 (forbidden), 404 (not found), 429 (rate limit). Checkpoint: Log e.message and the associated e.response body for debugging.
import os
import asana
from asana.error import AsanaError
client = asana.Client.access_token(os.environ["ASANA_PAT"])
client.options["client_name"] = "deployment-automation"
def create_deployment_task(
project_gid: str,
title: str,
assignee_gid: str,
due_on: str,
) -> dict | None:
"""Create a deployment tracking task in an Asana project."""
try:
task = client.tasks.create_in_workspace(
workspace_gid="123456789",
params={
"name": title,
"assignee": assignee_gid,
"projects": [project_gid],
"due_on": due_on,
"notes": "Automatically created by deployment pipeline.",
},
)
return task
except AsanaError as e:
print(f"Failed to create task: {e.status} — {e.message}")
return None
task = create_deployment_task(
project_gid="1200000000001234",
title="Deploy v2.5.0 to production",
assignee_gid="1200000000005678",
due_on="2026-06-01",
)
print(f"Created task: https://app.asana.com/0/0/{task['gid']}")
def list_project_tasks(project_gid: str) -> list[dict]:
"""List all tasks in a project with their custom field values."""
tasks = []
try:
for task in client.tasks.find_all(
project=project_gid,
iterator=True,
opt_fields="name,completed_at,assignee.name,custom_fields.name,custom_fields.display_value",
):
tasks.append(task)
except AsanaError as e:
print(f"Error fetching tasks: {e}")
return tasks
tasks = list_project_tasks("1200000000001234")
for t in tasks:
cf_values = {
cf["name"]: cf["display_value"]
for cf in t.get("custom_fields", [])
}
print(f"{t['name']} — assignee: {t.get('assignee', {}).get('name', 'unassigned')} — {cf_values}")
def add_task_comment(task_gid: str, comment_text: str) -> dict:
"""Append a comment to an Asana task."""
return client.tasks.add_comment(
task_gid=task_gid,
params={"text": comment_text},
)
add_task_comment("1200000000009999", "Deployment verified — all checks passed.")
def register_webhook(resource_gid: str, target_url: str) -> dict:
"""Register a webhook to receive change events for a project."""
webhook = client.webhooks.create(
resource=resource_gid,
target=target_url,
)
print(f"Webhook created: {webhook['gid']}")
print(f"Respond with 200 OK to {target_url}?verify={webhook['gid']}")
return webhook
# Example: receive notifications for all task changes in a project
register_webhook("1200000000001234", "https://my-app.com/webhooks/asana")
# ❌ BAD — bare except, no status differentiation
try:
task = client.tasks.create_in_workspace(workspace_gid="123", params={"name": "Bad"})
except Exception as e:
print("Something went wrong:", e)
# ✅ GOOD — typed AsanaError with status-based recovery
from asana.error import AsanaError, RateLimitError, NotFoundError
def safe_create_task(workspace_gid: str, params: dict) -> dict | None:
"""Create a task with resilient error handling."""
try:
return client.tasks.create_in_workspace(workspace_gid=workspace_gid, params=params)
except RateLimitError as e:
retry_after = int(e.response.headers.get("Retry-After", "5"))
print(f"Rate limited — retry after {retry_after}s")
return None
except NotFoundError:
print(f"Workspace {workspace_gid} not found — check permissions")
return None
except AsanaError as e:
print(f"Asana API error {e.status}: {e.message}")
return None
opt_fields — Asana returns minimal fields by defaultiterator=True) for large collectionsopt_fields without validating the field names against the API docsEvery integration function should expose:
Client.access_token() with token from envclient.tasks.*, client.projects.*, etc.opt_fields parameter for sparse responsesiterator=True generator or manual offset looptry/except AsanaError with status-specific logic| Skill | Purpose | |