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.