From rpw-building
Use when building Raycast extensions, especially with Python/uv dependencies, encountering venv corruption, authentication issues, or path race conditions - provides critical patterns from production fixes including .gitignore, lazy initialization, and validation
npx claudepluginhub randypitcherii/rpw-agent-marketplace --plugin rpw-buildingThis skill uses the workspace's default tool permissions.
Building Raycast extensions requires specific patterns to avoid corruption, race conditions, and authentication bugs. Official docs at https://developers.raycast.com.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
Building Raycast extensions requires specific patterns to avoid corruption, race conditions, and authentication bugs. Official docs at https://developers.raycast.com.
Core principle: Raycast copies your entire assets directory, including hidden files. This breaks Python venvs with absolute path symlinks.
Use when:
Critical symptoms this fixes:
ALWAYS check https://developers.raycast.com before implementing:
Use WebFetch to get current documentation when needed.
Problem: Raycast copies assets directory including .venv. Absolute path symlinks break.
Solution (4-step fix):
# In your-extension/.gitignore
assets/python/.venv/
Why: Exclude .venv from source so Raycast doesn't copy it. uv will create fresh venv at runtime.
// ❌ BAD: Race condition
const proxyPath = getProxyPath();
// ✅ GOOD: Lazy init with useState
const [proxyPath] = useState(() => getProxyPath());
Why: Prevents race condition where path accessed before initialization completes.
#!/bin/bash
cd "${proxyPath}" # Set working directory FIRST
export PATH="${uvPath}:${PATH}"
# Sync venv to ensure dependencies installed fresh
"${uvPath}" sync --quiet 2>/dev/null || true
# Now run the script
exec "${uvPath}" run python "${script}"
Why: Ensures uv sync and uv run execute in correct directory with access to pyproject.toml.
Why: With .venv excluded from source, must recreate it at runtime. uv sync installs dependencies from pyproject.toml.
# ❌ BAD: Profile settings override auth_type
config = Config(
host=workspace_url,
profile=profile,
auth_type="external-browser", # Gets overridden!
)
# ✅ GOOD: Only set host and auth_type
config = Config(
host=workspace_url,
auth_type="external-browser",
)
Why: Profile parameter causes Config to read ~/.databrickscfg, where auth_type setting overrides your explicit auth_type argument.
from typing import Union
class ChatMessage(BaseModel):
role: str
content: Union[str, list] # Both string and OpenAI multimodal
def get_text_content(self) -> str:
"""Extract text from string or multimodal format."""
if isinstance(self.content, str):
return self.content
# Handle [{"text": "...", "type": "text"}, ...]
text_parts = []
for item in self.content:
if isinstance(item, dict) and item.get("type") == "text":
text_parts.append(item.get("text", ""))
return "".join(text_parts)
Why: OpenAI API supports both string and array of content blocks. Support both for compatibility.
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
errors = exc.errors()
error_details = []
for error in errors:
loc = " -> ".join(str(x) for x in error["loc"])
msg = error["msg"]
error_details.append(f"{loc}: {msg}")
error_summary = "; ".join(error_details)
return JSONResponse(
status_code=422,
content={
"detail": errors,
"error_summary": error_summary,
"hint": "Check request format. Common: message content should be string or array.",
},
)
Why: Helpful error messages with hints make debugging faster.
| Issue | Solution | Why |
|---|---|---|
| Venv corruption | .gitignore .venv + uv sync | Raycast copies assets, breaks symlinks |
| Path race condition | useState(() => getPath()) | Lazy initialization prevents early access |
| Wrong working directory | cd in wrapper script | uv sync needs pyproject.toml location |
| Missing dependencies | uv sync before uv run | Recreate .venv with fresh install |
| Auth override | Don't pass profile to Config | Profile reads ~/.databrickscfg settings |
| Validation errors | Union[str, list] for content | OpenAI multimodal compatibility |
| Unhelpful errors | Custom error handler with hints | Faster debugging |
❌ "uv handles venv automatically, no need for .gitignore"
❌ "The path works in development, no need for lazy init"
❌ "Profile parameter is convenient for auth"
❌ "String content is fine, that's what examples show"
Before deploying Raycast extension with Python:
From production commits:
These patterns prevent production failures that only appear after Raycast's build/copy process.