ClawSkills logoClawSkills

Portable Tools

Build cross-device tools without hardcoding paths or account names

Introduction

# Portable Tools - Cross-Device Development Methodology

Methodology for building tools that work across different devices, naming schemes, and configurations. Based on lessons from OAuth refresher debugging session (2026-01-23).

## Core Principle

**Never assume your device is the only device.**

Your local setup is just one of many possible configurations. Build for the general case, not the specific instance.

---

## The Three Questions (Before Writing Code)

### 1. "What varies between devices?"

Before writing any code that reads configuration, data, or credentials:

**Ask:** - File paths? (macOS vs Linux, different home dirs) - Account names? (user123 vs default vs oauth) - Service names? (slight variations in spelling/capitalization) - Data structure? (different versions, different formats) - Environment? (different shells, different tools available)

**Example from OAuth refresher:** - ❌ Assumed: Account is always "claude" - ✅ Reality: Could be "claude", "Claude Code", "default", etc.

**Action:** List variables, make them configurable or auto-discoverable

---

### 2. "How do I prove this works?"

Before claiming success:

**Require:** - Concrete BEFORE state (exact values) - Concrete AFTER state (exact values) - Proof they're different (side-by-side comparison)

**Example from OAuth refresher:** ``` BEFORE: - Access Token: POp5z1fi...eSN9VAAA - Expires: 1769189639000

AFTER: - Access Token: 01v0RrFG...eOE9QAA ✅ Different - Expires: 1769190268000 ✅ Extended ```

**Action:** Always show data transformation with real values

---

### 3. "What happens when it breaks?"

Before pushing to production:

**Test:** - Wrong configuration (intentionally break config) - Missing data (remove expected fields) - Multiple entries (ambiguous case) - Edge cases (empty values, special characters)

**Example from OAuth refresher:** - Test with `keychain_account: "wrong-name"` → Fallback should work - Test with incomplete keychain data → Should fail gracefully with helpful error

**Action:** Test failure modes, not just happy path

---

## Mandatory Patterns

### Pattern 1: Explicit Over Implicit

**❌ Wrong:** ```bash # Ambiguous - returns first match security find-generic-password -s "Service" -w ```

**✅ Correct:** ```bash # Explicit - returns specific entry security find-generic-password -s "Service" -a "account" -w ```

**Rule:** If a command can be ambiguous, make it explicit.

---

### Pattern 2: Validate Before Use

**❌ Wrong:** ```bash DATA=$(read_config) USE_VALUE="$DATA" # Hope it's valid ```

**✅ Correct:** ```bash DATA=$(read_config) if ! validate_structure "$DATA"; then error "Invalid data structure" fi USE_VALUE="$DATA" ```

**Rule:** Never assume data has expected structure.

---

### Pattern 3: Fallback Chains

**❌ Wrong:** ```bash ACCOUNT="claude" # Hardcoded ```

**✅ Correct:** ```bash # Try configured → Try common → Error with help ACCOUNT="${CONFIG_ACCOUNT}" if ! has_data "$ACCOUNT"; then for fallback in "claude" "default" "oauth"; do if has_data "$fallback"; then ACCOUNT="$fallback" break fi done fi [[ -z "$ACCOUNT" ]] && error "No account found. Tried: ..." ```

**Rule:** Provide automatic fallbacks for common variations.

---

### Pattern 4: Helpful Errors

**❌ Wrong:** ```bash [[ -z "$TOKEN" ]] && error "No token" ```

**✅ Correct:** ```bash [[ -z "$TOKEN" ]] && error "No token found

Checked: - Config: $CONFIG_FILE - Field: $FIELD_NAME - Expected: { \"tokens\": { \"refresh\": \"...\" } }

Verify with: cat $CONFIG_FILE | jq '.tokens' " ```

**Rule:** Error messages should help user diagnose and fix.

---

## Debugging Methodology (Patrick's Approach)

### Step 1: Get Exact Data

**Don't ask:** "Is it broken?" **Ask:** "What exact values do you see? How many entries exist? Which one has the data?"

**Example:** ```bash # Vague "Check keychain"

# Specific "Run: security find-generic-password -l 'Service' | grep 'acct'" "Tell me: 1. How many entries 2. Which has tokens 3. Last modified" ```

---

### Step 2: Prove With Concrete Examples

**Don't say:** "It should work now" **Show:** "Here's the BEFORE token (POp5z...), here's AFTER (01v0R...), they're different"

**Template:** ``` BEFORE: - Field1: <exact_value> - Field2: <exact_value>

AFTER: - Field1: <new_value> ✅ Changed - Field2: <new_value> ✅ Changed

PROOF: Values are different ```

---

### Step 3: Think Cross-Device Immediately

**Don't think:** "Works on my machine" **Think:** "What if their setup differs in [X]?"

**Checklist:** - [ ] Different account names? - [ ] Different file paths? - [ ] Different tools/versions? - [ ] Different permissions? - [ ] Different data formats?

---

## Pre-Flight Checklist (Before Publishing)

### Discovery Phase - [ ] List all external dependencies (files, commands, services) - [ ] Document what each dependency provides - [ ] Identify which parts could vary between devices

### Implementation Phase - [ ] Make variations configurable (with sensible defaults) - [ ] Add validation for each input - [ ] Build fallback chains for common variations - [ ] Add `--dry-run` or `--test` mode

### Testing Phase - [ ] Test with correct config → Should work - [ ] Test with wrong config → Should fallback or fail gracefully - [ ] Test with missing data → Should give helpful error - [ ] Test with multiple entries → Should handle ambiguity

### Documentation Phase - [ ] Document default assumptions - [ ] Document how to verify local setup - [ ] Document common variations and how to handle them - [ ] Include data flow diagram - [ ] Add troubleshooting section

---

## Real-World Example: OAuth Refresher

### Original (Broken) ```bash # Assumes single entry, no validation, no fallback KEYCHAIN_DATA=$(security find-generic-password -s "Service" -w) REFRESH_TOKEN=$(echo "$KEYCHAIN_DATA" | jq -r '.refreshToken') # Use token (hope it's valid) ```

**Problems:** - Returns first alphabetical match (wrong entry) - No validation (could be empty/malformed) - No fallback (fails if account name differs)

---

### Fixed (Portable) ```bash # Explicit account with validation and fallback validate_data() { echo "$1" | jq -e '.claudeAiOauth.refreshToken' > /dev/null 2>&1 }

# Try configured account DATA=$(security find-generic-password -s "$SERVICE" -a "$ACCOUNT" -w 2>&1) if validate_data "$DATA"; then log "✓ Using account: $ACCOUNT" else log "⚠ Trying fallback accounts..." for fallback in "claude" "Claude Code" "default"; do DATA=$(security find-generic-password -s "$SERVICE" -a "$fallback" -w 2>&1) if validate_data "$DATA"; then ACCOUNT="$fallback" log "✓ Found data in: $fallback" break fi done fi

[[ -z "$DATA" ]] || ! validate_data "$DATA" && error "No valid data found Tried accounts: $ACCOUNT, claude, Claude Code, default Verify with: security find-generic-password -l '$SERVICE'"

REFRESH_TOKEN=$(echo "$DATA" | jq -r '.claudeAiOauth.refreshToken') ```

**Improvements:** - ✅ Explicit account parameter - ✅ Validates data structure - ✅ Automatic fallback to common names - ✅ Helpful error with verification command

---

## Common Anti-Patterns

### Anti-Pattern 1: "Works On My Machine" ```bash FILE="/Users/patrick/.config/app.json" # Hardcoded path ```

**Fix:** Use `$HOME`, detect OS, or make configurable

---

### Anti-Pattern 2: "Hope It's There" ```bash TOKEN=$(cat config.json | jq -r '.token') # What if .token doesn't exist? Script continues with empty value ```

**Fix:** Validate before using ```bash TOKEN=$(cat config.json | jq -r '.token // empty') [[ -z "$TOKEN" ]] && error "No token in config" ```

---

### Anti-Pattern 3: "First Match Is Right" ```bash # If multiple entries exist, which one? ENTRY=$(find_entry "service") ```

**Fix:** Be explicit or enumerate all ```bash ENTRY=$(find_entry "service" "account") # Specific # OR ALL=$(find_all_entries "service") for entry in $ALL; do validate_and_use "$entry" done ```

---

### Anti-Pattern 4: "Silent Failures" ```bash process_data || true # Ignore errors ```

**Fix:** Fail loudly with context ```bash process_data || error "Failed to process Data: $DATA Expected: { ... } Check: command_to_verify" ```

---

## Integration With Existing Workflows

### With sprint-plan.md Add to testing section: ```markdown ## Cross-Device Testing - [ ] Test with different account names - [ ] Test with wrong config values - [ ] Test with missing data - [ ] Document fallback behavior ```

### With PRIVACY-CHECKLIST.md Add before publishing: ```markdown ## Portability Check - [ ] No hardcoded paths (use $HOME, detect OS) - [ ] No hardcoded names (use config or fallback) - [ ] Validation on all inputs - [ ] Helpful errors for common issues ```

### With skill-creator When building new skills: 1. List what varies between devices 2. Make it configurable or auto-discoverable 3. Test with wrong config 4. Document troubleshooting

---

## Quick Reference Card

**Before writing code:** 1. What varies between devices? 2. How do I prove this works? 3. What happens when it breaks?

**Mandatory patterns:** - Explicit over implicit - Validate before use - Fallback chains - Helpful errors

**Testing:** - Correct config → Works - Wrong config → Fallback or helpful error - Missing data → Clear diagnostic

**Documentation:** - Data flow diagram - Common variations - Troubleshooting guide

---

## Success Criteria

A tool is **portable** when:

1. ✅ Works on different devices without modification 2. ✅ Auto-discovers common variations in setup 3. ✅ Fails gracefully with actionable error messages 4. ✅ Can be debugged by reading the error output 5. ✅ Documentation covers "what if my setup differs"

**Test:** Give it to someone with a different setup. If they need to ask you questions, the tool isn't portable yet.

---

## Origin Story

This methodology emerged from debugging the OAuth refresher (2026-01-23): - Script read wrong keychain entry (didn't specify account) - Assumed single entry existed (multiple did) - No validation (used empty data) - No fallback (failed on different account names)

Patrick's approach: 1. Asked for exact data (how many entries, which has tokens) 2. Demanded proof (show BEFORE/AFTER tokens) 3. Thought cross-device (what if naming differs?)

Result: Tool went from single-device/broken to universal/production-ready.

**Key insight:** The bugs weren't in the logic - they were in the assumptions.

---

## When To Use This Skill

**Use when:** - Building tools that read system configuration - Working with keychains, credentials, environment variables - Creating scripts that run on multiple machines - Publishing skills to ClawdHub (others will use them)

**Apply:** 1. Before implementing: Answer the three questions 2. During implementation: Use mandatory patterns 3. Before testing: Run pre-flight checklist 4. After testing: Document variations and troubleshooting

**Remember:** Your device is just one case. Build for the general case.

More Products