
Doppler Workflows
- 118 installs
- 62 repo stars
- Updated August 3, 2026
- terrylica/cc-skills
Use doppler-workflows for development tasks
About
doppler-workflows: A skill for development. This provides functionality for development workflows.
- doppler-workflows
Doppler Workflows by the numbers
- 118 all-time installs (skills.sh)
- Ranked #2,862 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/terrylica/cc-skills --skill doppler-workflowsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 118 |
|---|---|
| repo stars | ★ 62 |
| Last updated | August 3, 2026 |
| Repository | terrylica/cc-skills ↗ |
What it does
Use doppler-workflows for development tasks
Files
Doppler Credential Workflows
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
When to Use This Skill
Use this skill when:
- Publishing Python packages to PyPI
- Rotating AWS access keys
- Managing credentials across multiple services
- Troubleshooting authentication failures (403, InvalidClientTokenId)
- Setting up Doppler credential injection patterns
- Multi-token/multi-account strategies
Quick Reference
Core Pattern: Doppler CLI
Standard Usage:
doppler run --project <project> --config <config> --command='<command>'Why --command flag:
- Official Doppler pattern (auto-detects shell)
- Ensures variables expand AFTER Doppler injects them
- Without it: shell expands
$VARbefore Doppler runs → empty string
---
Quick Start Examples
PyPI Publishing
doppler run --project claude-config --config dev \
--command='uv publish --token "$PYPI_TOKEN"'AWS Operations
doppler run --project aws-credentials --config dev \
--command='aws s3 ls --region $AWS_DEFAULT_REGION'---
Best Practices
1. Always use --command flag for credential injection 2. Use project-scoped tokens (PyPI) for better security 3. Rotate credentials regularly (90 days recommended) 4. Document with Doppler notes: doppler secrets notes set <SECRET> "<note>" 5. Use stdin for storing secrets: echo -n 'secret' | doppler secrets set 6. Test injection before using: echo ${#VAR} to verify length 7. Multi-token naming: SERVICE_TOKEN_{ABBREV} for clarity
---
Reference Documentation
For detailed information, see:
- PyPI Publishing - Token setup, publishing, troubleshooting
- AWS Credentials - Rotation workflow, setup, troubleshooting
- Multi-Service Patterns - Multiple PyPI packages, multiple AWS accounts
- AWS Workflow - Complete AWS credential management guide
Bundled Specifications:
PYPI_REFERENCE.yaml- Complete PyPI specAWS_SPECIFICATION.yaml- AWS credential architecture
---
Using mise [env] for Local Development (Recommended)
For local development, mise [env] provides a simpler alternative to doppler run:
# .mise.toml
[env]
# Fetch from Doppler with caching for performance
PYPI_TOKEN = "{{ cache(key='pypi_token', duration='1h', run='doppler secrets get PYPI_TOKEN --project claude-config --config prd --plain') }}"
# For GitHub multi-account setups
GH_TOKEN = "{{ read_file(path=env.HOME ~ '/.claude/.secrets/gh-token-accountname') | trim }}"When to use mise [env]:
- Per-directory credential configuration
- Multi-account GitHub setups
- Credentials that persist across commands (not session-scoped)
When to use doppler run:
- CI/CD pipelines
- Single-command credential scope
- When you want credentials auto-cleared after command
See `mise-configuration` skill for complete patterns.
---
PyPI Publishing Policy
<!-- ADR: 2025-12-10-clickhouse-skill-documentation-gaps -->
For PyPI publishing, see `pypi-doppler` skill for LOCAL-ONLY workspace policy.
Do NOT configure PyPI publishing in GitHub Actions or CI/CD pipelines.
---
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| 403 on PyPI publish | Token expired or wrong scope | Regenerate project-scoped token, update in Doppler |
| InvalidClientTokenId (AWS) | Access key rotated or deleted | Run AWS key rotation workflow, update Doppler |
| Variable expands empty | Using $VAR without --command | Always use --command='...$VAR...' pattern |
| Doppler CLI not found | Not installed | brew install dopplerhq/cli/doppler |
| Wrong config selected | Ambiguous project/config | Specify both --project and --config explicitly |
| mise [env] not loading | Not in directory with .mise.toml | cd to project directory or check mise.toml path |
| Secret retrieval slow | No caching configured | Use mise cache() with duration for repeated access |
| Token length mismatch | Copied with extra whitespace | Trim token: `echo -n 'secret' \ |
Post-Execution Reflection
After this skill completes, check before closing:
1. Did the command succeed? — If not, fix the instruction or error table that caused the failure. 2. Did parameters or output change? — If the underlying tool's interface drifted, update Usage examples and Parameters table to match. 3. Was a workaround needed? — If you had to improvise (different flags, extra steps), update this SKILL.md so the next invocation doesn't need the same workaround.
Only update if the issue is real and reproducible — not speculative.
openapi: 3.1.0
info:
title: AWS Credentials Management with Doppler
description: Complete workflow for secure AWS credential storage, rotation, and documentation using Doppler
version: 1.0.0
components:
schemas:
DopplerProject:
type: object
description: Doppler project structure for AWS credentials
properties:
project_name:
type: string
example: "aws-credentials"
configs:
type: array
items:
type: string
example: ["dev", "staging", "production"]
secrets:
type: object
properties:
credentials:
type: array
items:
type: string
example:
- "AWS_ACCESS_KEY_ID"
- "AWS_SECRET_ACCESS_KEY"
- "AWS_DEFAULT_REGION"
- "AWS_ACCOUNT_ID"
archived:
type: array
items:
type: string
example:
- "AWS_ACCESS_KEY_ID_OLD"
- "AWS_SECRET_ACCESS_KEY_OLD"
documentation:
type: array
items:
type: string
example:
- "AWS_ACCESS_INVENTORY_REPORT"
- "AWS_ACCESS_SUMMARY"
- "AWS_LAST_AUDIT_DATE"
CredentialRotation:
type: object
description: Safe AWS credential rotation workflow
properties:
prerequisites:
type: object
properties:
max_keys_per_user:
type: integer
example: 2
current_key_count:
type: integer
example: 1
available_slots:
type: integer
example: 1
workflow:
type: array
items:
type: object
properties:
phase:
type: string
status:
type: string
actions:
type: array
items:
type: string
example:
- phase: "Store Old Credentials"
status: "non-destructive"
actions:
- "Store old key as AWS_ACCESS_KEY_ID_OLD"
- "Add notes with exposure date and retention policy"
- phase: "Create New Credentials"
status: "zero-exposure"
actions:
- "aws iam create-access-key --output json"
- "Pipe directly to Doppler (never displayed)"
- "Securely delete temp files"
- phase: "Testing Period"
status: "both-keys-active"
actions:
- "Test new credential via Doppler"
- "Old credential remains functional"
- "Compare access side-by-side"
- phase: "Cleanup"
status: "user-approval-required"
actions:
- "Delete old AWS IAM key"
- "Update Doppler notes"
SecretDocumentation:
type: object
description: Doppler secret annotation and documentation
properties:
notes:
type: object
properties:
command:
type: string
example: "doppler secrets notes set <SECRET_NAME> \"<note>\" --project <project>"
purpose:
type: array
items:
type: string
example:
- "Historical context"
- "Retention policies"
- "Rotation dates"
- "Usage instructions"
- "Security notes"
activity_logs:
type: object
properties:
tracks:
type: array
items:
type: string
example:
- "Who made changes"
- "When changes occurred"
- "What was changed"
access:
type: string
example: "doppler activity --project <project>"
versioning:
type: object
properties:
retention:
type: string
example: "Lifetime of secret (until deleted)"
features:
type: array
items:
type: string
example:
- "Full change history"
- "Rollback capability"
- "Git-style versioning"
AccessAudit:
type: object
description: Read-only AWS access inventory and verification
properties:
safety_guarantees:
type: array
items:
type: string
example:
- "Only list/describe/get commands"
- "Zero write operations"
- "No resource modifications"
- "Pure inventory gathering"
services_audited:
type: array
items:
type: string
example:
- "IAM"
- "S3"
- "Lambda"
- "DynamoDB"
- "ECS/ECR"
- "CloudWatch"
- "SNS/SQS"
- "API Gateway"
- "CodeArtifact"
- "RDS"
- "EC2"
reports:
type: object
properties:
json_report:
type: string
description: "Machine-readable inventory"
example: "AWS_ACCESS_INVENTORY_REPORT"
summary:
type: string
description: "Human-readable summary"
example: "AWS_ACCESS_SUMMARY"
timestamp:
type: string
description: "Last audit date"
example: "AWS_LAST_AUDIT_DATE"
UsagePatterns:
type: object
description: Standard Doppler + AWS CLI usage patterns
properties:
basic_usage:
type: string
example: "doppler run --project aws-credentials --config dev -- aws <command>"
examples:
type: array
items:
type: object
properties:
description:
type: string
command:
type: string
example:
- description: "List S3 buckets"
command: "doppler run --project aws-credentials --config dev -- aws s3 ls"
- description: "Get caller identity"
command: "doppler run --project aws-credentials --config dev -- aws sts get-caller-identity"
- description: "Run Python script with credentials"
command: "doppler run --project aws-credentials --config dev -- python script.py"
query_documentation:
type: array
items:
type: string
example:
- "doppler secrets --project aws-credentials --config dev"
- "doppler secrets get AWS_ACCESS_SUMMARY --plain"
- "doppler activity --project aws-credentials"
paths: {}
AWS Credentials Management with Doppler
Secure credential storage, rotation, and documentation workflow
---
Overview
This workflow manages AWS IAM credentials using Doppler for secure storage, rotation, and comprehensive documentation. All credentials are stored with contextual notes for future reference.
Key Features:
- ✅ Zero-exposure credential creation (never displayed on screen)
- ✅ Safe dual-key rotation (old key remains active during testing)
- ✅ Comprehensive access audits (read-only, non-intrusive)
- ✅ Full documentation in Doppler (notes, activity logs, versioning)
---
Doppler Project Structure
Project: aws-credentials Config: dev (or staging, production)
Secrets:
AWS_ACCESS_KEY_ID # Active primary credential
AWS_SECRET_ACCESS_KEY # Active primary credential
AWS_ACCESS_KEY_ID_OLD # Archived for rollback (30-day retention)
AWS_SECRET_ACCESS_KEY_OLD # Archived for rollback
AWS_DEFAULT_REGION # Primary AWS region
AWS_ACCOUNT_ID # AWS account ID (for reference)
AWS_ACCESS_INVENTORY_REPORT # Full JSON audit report
AWS_ACCESS_SUMMARY # Human-readable access summary
AWS_LAST_AUDIT_DATE # Last audit timestampAll secrets include detailed notes explaining their purpose, history, and usage.
---
Credential Rotation Workflow
Prerequisites
- AWS allows max 2 access keys per IAM user
- Current setup: 1 active key → 1 available slot
- Both keys attached to same IAM user = identical permissions
Phase 1: Store Old Credentials
Store existing credentials for rollback safety:
# Store old credentials
echo 'AKIAXXXXXXXXXX' | doppler secrets set AWS_ACCESS_KEY_ID_OLD --project aws-credentials --config dev
echo 'old-secret-key' | doppler secrets set AWS_SECRET_ACCESS_KEY_OLD --project aws-credentials --config dev
# Document retention policy
doppler secrets notes set AWS_ACCESS_KEY_ID_OLD \
"DEPRECATED - Created 2025-XX-XX, exposed 2025-XX-XX. Kept for 30-day rollback period. AWS IAM key remains ACTIVE during testing. Delete after 2025-XX-XX." \
--project aws-credentialsPhase 2: Create New Credentials (Zero Exposure)
Create new AWS access key and pipe directly to Doppler:
/usr/bin/env bash << 'CONFIG_EOF'
# Create key and save to temp file
aws iam create-access-key --user-name <username> --profile <profile> --output json > /tmp/new_aws_key.json
# Extract and store in Doppler (never displayed)
cat /tmp/new_aws_key.json | jq -r '.AccessKey.AccessKeyId' | \
doppler secrets set AWS_ACCESS_KEY_ID --project aws-credentials --config dev --silent
cat /tmp/new_aws_key.json | jq -r '.AccessKey.SecretAccessKey' | \
doppler secrets set AWS_SECRET_ACCESS_KEY --project aws-credentials --config dev --silent
# Securely delete temp file
shred -u /tmp/new_aws_key.json 2>/dev/null || rm -f /tmp/new_aws_key.json
# Document the new credential
doppler secrets notes set AWS_ACCESS_KEY_ID \
"PRIMARY - Created $(date +%Y-%m-%d) via secure rotation. Replaces AWS_ACCESS_KEY_ID_OLD after security exposure. This is the active production credential." \
--project aws-credentials
CONFIG_EOFPhase 3: Testing Period (Both Keys Active)
Verify both keys work:
# Verify both keys exist in AWS IAM
aws iam list-access-keys --user-name <username> --profile <profile>
# Should show 2 active keys
# Test new credential
doppler run --project aws-credentials --config dev -- aws sts get-caller-identity
doppler run --project aws-credentials --config dev -- aws s3 ls
# Test old credential (still works)
aws sts get-caller-identity --profile <profile>Both keys have identical permissions - test thoroughly before deletion.
Phase 4: Cleanup (User Approval Required)
⚠️ Only proceed after confirming new credential works perfectly
/usr/bin/env bash << 'DOPPLER_EOF'
# Delete old AWS IAM key
aws iam delete-access-key --access-key-id AKIAXXXXXXXXXX --user-name <username> --profile <profile>
# Update Doppler notes
doppler secrets notes set AWS_ACCESS_KEY_ID_OLD \
"ARCHIVED - Created 2025-XX-XX, exposed 2025-XX-XX, AWS IAM key deleted $(date +%Y-%m-%d). Kept for audit trail. No longer valid in AWS." \
--project aws-credentials
DOPPLER_EOF---
Access Audit & Inventory
Read-Only Audit Commands
100% safe - only list/describe/get operations, zero modifications:
# IAM permissions
aws iam get-account-authorization-details --output json
aws iam list-attached-user-policies --user-name <username>
aws iam list-groups-for-user --user-name <username>
# Resource inventory
aws s3api list-buckets
aws lambda list-functions --region us-west-2
aws dynamodb list-tables --region us-west-2
aws ecs list-clusters --region us-west-2
aws ecr describe-repositories --region us-west-2
aws cloudwatch describe-alarms --region us-west-2
aws logs describe-log-groups --region us-west-2 --max-items 100Generate Comprehensive Report
/usr/bin/env bash << 'CONFIG_EOF_2'
# Run comprehensive audit (15+ services)
# Store results in /tmp/aws_*.json files
# Generate JSON report: /tmp/aws_access_report.json
# Generate summary: /tmp/aws_access_summary.txt
# Store in Doppler
cat /tmp/aws_access_report.json | doppler secrets set AWS_ACCESS_INVENTORY_REPORT --project aws-credentials --config dev
cat /tmp/aws_access_summary.txt | doppler secrets set AWS_ACCESS_SUMMARY --project aws-credentials --config dev
date -u +"%Y-%m-%dT%H:%M:%SZ" | doppler secrets set AWS_LAST_AUDIT_DATE --project aws-credentials --config dev
# Add documentation notes
doppler secrets notes set AWS_ACCESS_INVENTORY_REPORT \
"Complete AWS resource inventory (JSON format) generated $(date +%Y-%m-%d). Documents all accessible services, resources, IAM permissions, and credential verification results. Generated via read-only audit commands. Use this for: compliance audits, permission reviews, onboarding documentation." \
--project aws-credentials
CONFIG_EOF_2---
Usage Patterns
Basic Usage
# General pattern
doppler run --project aws-credentials --config dev -- aws <command>
# Examples
doppler run --project aws-credentials --config dev -- aws s3 ls
doppler run --project aws-credentials --config dev -- aws lambda list-functions --region us-west-2
doppler run --project aws-credentials --config dev -- python my_aws_script.pyQuery Documentation
# View all secrets with notes
doppler secrets --project aws-credentials --config dev
# View specific report
doppler secrets get AWS_ACCESS_SUMMARY --project aws-credentials --config dev --plain
doppler secrets get AWS_ACCESS_INVENTORY_REPORT --project aws-credentials --config dev --plain | jq .
# View activity history
doppler activity --project aws-credentials
# Dashboard access
open https://dashboard.doppler.com---
Stored Reports
AWS_ACCESS_INVENTORY_REPORT (JSON)
Machine-readable inventory with:
- IAM permissions and policies
- Resource counts by service
- Detailed resource lists
- Credential verification results
- Audit metadata
Use for: Automation, compliance reporting, detailed analysis
AWS_ACCESS_SUMMARY (Text)
Human-readable summary with:
- IAM user and permissions
- Resource inventory counts
- Key resources by service
- Credential comparison results
- Access level explanation
Use for: Quick reference, onboarding, permission review
AWS_LAST_AUDIT_DATE (Timestamp)
Last comprehensive audit date.
Recommended cadence: Quarterly or after permission changes
---
Security Best Practices
Credential Handling
✅ DO:
- Use Doppler for all credential storage
- Pipe credentials directly to Doppler (never display)
- Test thoroughly before deleting old credentials
- Document all changes with notes
- Rotate credentials quarterly or when exposed
✗ DON'T:
- Store credentials in plain text files
- Display credentials on screen
- Share credentials via chat/email
- Delete old credentials without testing new ones
- Skip documentation/notes
Audit Safety
✅ Safe commands (read-only):
list-*- List resourcesdescribe-*- Describe resourcesget-*- Get resource details
✗ Avoid during audits:
create-*,delete-*,update-*- Modify resourcesput-*- Write operations- Any command without
--dry-runflag when available
---
Account Details
Current Setup:
- IAM User:
terryli - Account ID:
050214414362(EonLabs) - Group:
fullstack-eng - Region:
us-west-2
Effective Permissions: Near-Administrator
- ✅ Full access to S3, Lambda, DynamoDB, ECS, ECR, CloudWatch
- ✅ Can create/modify/delete most resources
- ✗ Cannot modify IAM users/policies
- ✗ Cannot modify AWS Organizations
Key Resources:
- 33 S3 buckets (ML models, portfolios, predictions)
- 28 Lambda functions (Touchstone, Cron, Realtime)
- 19 DynamoDB tables (ModelPredictions, TradeModels, etc.)
- 2 ECS clusters, 3 ECR repositories
---
Troubleshooting
"Config not found"
# List available configs
doppler configs --project aws-credentials
# Doppler defaults to dev/stg/prd
# Use --config flag explicitly if using custom namesCredentials not working
# Verify Doppler is injecting credentials
doppler run --project aws-credentials --config dev -- env | grep AWS
# Test with simple command
doppler run --project aws-credentials --config dev -- aws sts get-caller-identity
# Check AWS CLI version
aws --versionNeed to rollback to old credential
/usr/bin/env bash << 'CONFIG_EOF_3'
# Copy OLD credentials to primary names
doppler secrets get AWS_ACCESS_KEY_ID_OLD --plain | \
doppler secrets set AWS_ACCESS_KEY_ID --project aws-credentials --config dev
doppler secrets get AWS_SECRET_ACCESS_KEY_OLD --plain | \
doppler secrets set AWS_SECRET_ACCESS_KEY --project aws-credentials --config dev
# Update notes
doppler secrets notes set AWS_ACCESS_KEY_ID \
"ROLLED BACK - Restored from AWS_ACCESS_KEY_ID_OLD on $(date +%Y-%m-%d) due to [reason]" \
--project aws-credentials
CONFIG_EOF_3---
Related Documentation
Hub (User Memory):
- User Memory:
~/.claude/CLAUDE.md- Global workspace configuration and conventions - Documentation Index:
docs/INDEX.md- Hub-and-spoke navigation
Specifications (Machine-Readable):
- Doppler Integration:
specifications/doppler-integration.yaml- OpenAPI 3.1.0 spec - AWS Credentials Management:
specifications/aws-credentials-management.yaml- Complete workflow spec
Setup Guides (Human-Readable):
- This Document: Rotation and usage workflows
- Elimination Plan:
docs/setup/aws-credentials-elimination.md- Reference implementation for ml-feature-experiments
---
Last Updated: 2025-10-11
openapi: 3.1.0
info:
title: PyPI Publishing Methods Specification
description: Active PyPI package publishing via Doppler credential management
version: 2.4.0
x-changelog:
- version: 2.4.0
date: 2025-10-07
changes:
- CORRECTION: --command flag is the official Doppler-recommended method (not bash -c)
- Added comparison of all three approaches (--command, bash -c, direct)
- Confirmed --command auto-detects shell and is cleaner/more portable
- bash -c still works but --command is the idiomatic Doppler pattern
- Updated all examples to use --command flag
- version: 2.3.0
date: 2025-10-07
changes:
- Added second project-scoped token for gapless-crypto-clickhouse (gcd token)
- Documented multi-package token management strategy
- Confirmed Doppler + bash -c pattern works for all project-scoped tokens
- Updated examples to show per-package token variable naming
- version: 2.2.0
date: 2025-10-06
changes:
- DECISION: Use project-scoped tokens exclusively (account-wide tokens fail with 403)
- Removed accountwideall token (failed testing despite being newly created)
- Confirmed bash -c wrapper required for Doppler environment variable expansion
- Added PyPI endpoint documentation (legacy endpoint is current standard)
- version: 2.1.0
date: 2025-10-06
changes:
- Updated token from "terrylica-entire-account" to "aal" (project-scoped)
- Added troubleshooting section for credential verification
- Documented Doppler display vs actual value behavior
- Added best practices for token management
components:
schemas:
DopplerTokenMethod:
type: object
description: Doppler-managed PyPI tokens (ACTIVE METHOD - use this exclusively)
properties:
status:
type: string
enum: ["primary_method"]
storage:
type: object
properties:
service:
type: string
example: "Doppler"
project:
type: string
example: "claude-config"
config:
type: string
example: "dev"
secret_naming_convention:
type: string
example: "PYPI_TOKEN_{PACKAGE_ABBREV}"
description: "Use package abbreviation suffix for multiple tokens"
active_tokens:
type: object
description: "Currently active project-scoped tokens"
properties:
atr_adaptive_laguerre:
type: object
properties:
doppler_secret_name:
type: string
example: "PYPI_TOKEN"
token_identifier:
type: string
example: "c1d5867e-0d0e-4d8c-b409-04a8cc58cb40"
token_name:
type: string
example: "aal"
scope:
type: string
example: "Project 'atr-adaptive-laguerre'"
permissions:
type: string
example: "Upload packages"
created:
type: string
example: "2025-10-06"
last_tested:
type: string
example: "2025-10-06"
status:
type: string
example: "✅ WORKING"
gapless_crypto_clickhouse:
type: object
properties:
doppler_secret_name:
type: string
example: "PYPI_TOKEN_GCD"
token_identifier:
type: string
example: "7f807b10-5057-44da-b0b8-cd6f3e974fb9"
token_name:
type: string
example: "gcd"
scope:
type: string
example: "Project 'gapless-crypto-clickhouse'"
permissions:
type: string
example: "Upload packages"
created:
type: string
example: "2025-10-07"
last_tested:
type: string
example: "2025-10-07"
status:
type: string
example: "✅ WORKING"
token_management_url:
type: string
example: "https://pypi.org/manage/account/token/"
usage:
type: object
properties:
publish_examples:
type: object
description: "Per-package publish commands using official --command flag"
properties:
atr_adaptive_laguerre:
type: string
example: "doppler run --project claude-config --config dev --command='uv publish --token \"$PYPI_TOKEN\"'"
description: "Publish atr-adaptive-laguerre using PYPI_TOKEN (--command is idiomatic)"
gapless_crypto_clickhouse:
type: string
example: "doppler run --project claude-config --config dev --command='uv publish --token \"$PYPI_TOKEN_GCD\"'"
description: "Publish gapless-crypto-clickhouse using PYPI_TOKEN_GCD (--command is idiomatic)"
method_comparison:
type: object
description: "Three ways to use doppler run - tested and verified"
properties:
recommended_command_flag:
type: object
properties:
example:
type: string
example: "doppler run --command='uv publish --token \"$VAR\"'"
status:
type: string
example: "✅ RECOMMENDED - Official Doppler method"
benefits:
type: array
items:
type: string
example:
- "Auto-detects shell from $SHELL environment variable"
- "Supports sh, bash, zsh, dash, fish, ksh, tcsh, csh"
- "Cleaner syntax than bash -c"
- "Official Doppler documentation pattern"
- "Portable across different user shells"
alternative_bash_c:
type: object
properties:
example:
type: string
example: "doppler run -- bash -c 'uv publish --token \"$VAR\"'"
status:
type: string
example: "✅ WORKS - But less idiomatic"
when_to_use:
type: array
items:
type: string
example:
- "When you specifically need bash syntax"
- "When you want fresh shell without user's .bashrc"
- "Legacy scripts already using this pattern"
incorrect_direct:
type: object
properties:
example:
type: string
example: "doppler run -- uv publish --token \"$VAR\""
status:
type: string
example: "❌ FAILS - Variable expanded too early"
reason:
type: string
example: "Shell expands $VAR BEFORE Doppler injects it, resulting in empty string"
setup_commands:
type: object
properties:
add_token:
type: string
example: "echo -n 'pypi-...' | doppler secrets set PYPI_TOKEN_XXX --project claude-config --config dev"
description: "Use stdin to avoid shell escaping issues"
verify_storage:
type: string
example: "doppler secrets get PYPI_TOKEN_XXX --project claude-config --config dev --plain | head -c 50"
description: "Check token stored correctly (starts with pypi-AgEI...)"
test_injection:
type: string
example: "doppler run --project claude-config --config dev --command='echo ${#PYPI_TOKEN_XXX}'"
description: "Verify injection works (should show 221-224) using --command flag"
troubleshooting:
type: object
properties:
display_vs_actual:
type: object
properties:
issue:
type: string
example: "doppler secrets get adds newline to display"
explanation:
type: string
example: "When you run 'doppler secrets get PYPI_TOKEN --plain', it shows a trailing newline (0x0a). This is ONLY for display formatting. The actual value stored in Doppler and passed via 'doppler run' does NOT have the newline."
test_command:
type: string
example: "doppler run --project claude-config --config dev -- bash -c 'printf \"%s\" \"$PYPI_TOKEN\" | wc -c'"
expected_result:
type: string
example: "224 (no trailing newline)"
token_expiry:
type: object
properties:
symptom:
type: string
example: "403 Forbidden: Invalid or non-existent authentication information"
cause:
type: string
example: "PyPI tokens can be revoked or expire. Check PyPI web interface for token status."
solution:
type: string
example: "Create new project-scoped token on PyPI, copy exact value, update Doppler with: echo -n 'pypi-...' | doppler secrets set PYPI_TOKEN --project claude-config --config dev"
verification_workflow:
type: array
items:
type: object
example:
- step: 1
action: "Check token in PyPI web interface"
command: "Open https://pypi.org/manage/account/token/"
verify: "Token shows 'Last used: recently' or create new token"
- step: 2
action: "Verify token in Doppler"
command: "doppler secrets get PYPI_TOKEN --project claude-config --config dev --plain | head -c 50"
verify: "Starts with 'pypi-AgEIcHlwaS5vcmcC...'"
- step: 3
action: "Test credential injection"
command: "doppler run --project claude-config --config dev --command='echo Token length: ${#PYPI_TOKEN}'"
verify: "Shows 224 (or similar length for valid token)"
- step: 4
action: "Test publish with Doppler"
command: "doppler run --project claude-config --config dev --command='uv publish --token \"$PYPI_TOKEN\" dist/*.whl'"
verify: "Gets 200 OK (or 400 if file already exists, which is fine)"
best_practices:
type: object
properties:
doppler_command_flag:
type: string
example: "Use --command flag when environment variables are used as command arguments: doppler run --command='command --flag \"$VAR\"'"
reason: "The --command flag is the official Doppler pattern. It auto-detects your shell and ensures variables expand AFTER Doppler injects them. Without --command (or bash -c), the shell expands $VAR before Doppler runs, resulting in empty variable."
token_scope:
type: string
example: "Use project-scoped tokens (e.g., 'aal' for atr-adaptive-laguerre, 'gcd' for gapless-crypto-clickhouse) instead of account-wide tokens"
reason: "Limits blast radius if token is compromised, easier to audit per-package access"
multi_package_strategy:
type: object
description: "Managing tokens for multiple PyPI packages"
properties:
doppler_naming:
type: string
example: "PYPI_TOKEN (first package), PYPI_TOKEN_{ABBREV} (additional packages)"
description: "Use package abbreviation suffix for clarity"
pypi_token_naming:
type: string
example: "aal (atr-adaptive-laguerre), gcd (gapless-crypto-clickhouse)"
description: "Create memorable abbreviations on PyPI side"
usage_pattern:
type: string
example: "Each project uses its own token variable: $PYPI_TOKEN for aal, $PYPI_TOKEN_GCD for gcd"
description: "Makes it clear which token is being used in publish commands"
token_naming:
type: string
example: "Use descriptive abbreviations so you can identify which token is for which package"
reason: "Easier token management when you have multiple packages"
token_storage:
type: string
example: "ALWAYS use Doppler for credential storage. Never commit tokens to git, never store in .pypirc"
reason: "Centralized, version-controlled credential management"
token_updates:
type: string
example: "When updating tokens in Doppler, use stdin to avoid shell escaping: echo -n 'token' | doppler secrets set"
reason: "Prevents shell from interpreting special characters in token"
testing:
type: string
example: "After updating tokens, test with 'doppler run --command' to verify injection works BEFORE attempting actual publish"
reason: "Catch configuration issues early, before attempting to publish. Use --command flag for idiomatic Doppler usage."
pypi_endpoints:
type: object
description: "PyPI upload endpoint information"
properties:
legacy_endpoint:
type: object
properties:
url:
type: string
example: "https://upload.pypi.org/legacy/"
status:
type: string
example: "ACTIVE - This is the current production endpoint"
name_confusion:
type: string
example: "Despite being named 'legacy', this is the ONLY production upload endpoint for PyPI as of 2025"
explanation:
type: string
example: "The name 'legacy' refers to the API being copied from pypi-legacy (old PyPI codebase). The current PyPI (Warehouse) implements this same API at the /legacy/ path. This is the standard upload method used by twine, uv, and all other tools."
will_it_change:
type: string
example: "PyPI has no specific plans to drop this API, though PEP 694 proposes an Upload 2.0 API for future use"
upload_2_api:
type: object
properties:
status:
type: string
example: "PROPOSED (PEP 694) - Not yet implemented"
benefits:
type: string
example: "Supports parallel/resumable uploads for large files (>1GB), async processing"
availability:
type: string
example: "Not yet available on production PyPI as of 2025"
reference:
type: string
example: "https://peps.python.org/pep-0694/"
HistoricalTokens:
type: object
description: Historical token information for reference
properties:
terrylica_entire_account:
type: object
properties:
token_id:
type: string
example: "2b59ad01-34b4-4425-b7a0-9ab49046ea4e"
status:
type: string
example: "NOT WORKING - Returns 403 Forbidden when tested 2025-10-06"
scope:
type: string
example: "Entire account (all projects)"
test_results:
type: string
example: "Tested with proper Doppler injection (doppler run -- bash -c) - still returns 403. Token appears in PyPI UI but is not functional."
possible_causes:
type: array
items:
type: string
example:
- "Token may have been revoked on PyPI side"
- "Token may have expired (PyPI doesn't show expiry in UI)"
- "Token permissions may have been changed"
- "Token may be in invalid state despite showing in UI"
reason_for_replacement:
type: string
example: "Token non-functional despite appearing in PyPI UI. Replaced with working project-scoped 'aal' token."
last_tested:
type: string
example: "2025-10-06 - 403 Forbidden with both direct use and Doppler injection"
publishing:
type: object
properties:
token_id:
type: string
example: "Unknown"
status:
type: string
example: "DEPRECATED - expired/revoked"
created:
type: string
example: "2025-09-09"
last_used:
type: string
example: "2025-10-06"
reason_for_replacement:
type: string
example: "Token expired or revoked by PyPI"
DeprecatedMethods:
type: object
description: Deprecated publishing methods - DO NOT USE
properties:
pypirc:
type: object
properties:
status:
type: string
enum: ["deprecated"]
reason:
type: string
example: "Tokens expire frequently, not version controlled, stored in plaintext"
location:
type: string
example: "$HOME/.pypirc (DO NOT USE)"
migration:
type: string
example: "Token already migrated to Doppler (claude-config/dev/PYPI_TOKEN)"
trusted_publishing:
type: object
properties:
status:
type: string
enum: ["future_consideration"]
reason:
type: string
example: "OIDC-based GitHub Actions workflow - not yet implemented"
documentation:
type: string
example: "https://docs.pypi.org/trusted-publishers/"
benefits:
type: string
example: "No long-lived tokens, automatic credential rotation, better security"
consideration:
type: string
example: "Requires GitHub Actions setup and per-package PyPI configuration"
paths: {}
Skill: Doppler Credential Workflows
Use Case 2: AWS Credential Management
Quick Start
# Use AWS credentials
doppler run --project aws-credentials --config dev \
--command='aws s3 ls --region $AWS_DEFAULT_REGION'Credential Setup
Doppler Storage:
- Project:
aws-credentials - Configs:
dev,staging,prod(one per AWS account)
Required Secrets:
AWS_ACCESS_KEY_ID # IAM access key (20 chars)
AWS_SECRET_ACCESS_KEY # IAM secret (40 chars)
AWS_DEFAULT_REGION # e.g., us-east-1
AWS_ACCOUNT_ID # For audit trail
AWS_LAST_ROTATED_DATE # Timestamp
AWS_ROTATION_INTERVAL_DAYS # e.g., 90AWS Rotation Workflow
Step 1: Create New Credentials
# In AWS IAM Console:
# Users → Select user → Security credentials → Create access keyStep 2: Store in Doppler
/usr/bin/env bash << 'CONFIG_EOF'
echo -n 'AKIAIOSFODNN7EXAMPLE' | doppler secrets set AWS_ACCESS_KEY_ID \
--project aws-credentials --config dev
echo -n 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' | \
doppler secrets set AWS_SECRET_ACCESS_KEY \
--project aws-credentials --config dev
doppler secrets set AWS_LAST_ROTATED_DATE \
--project aws-credentials --config dev \
--value "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
CONFIG_EOFStep 3: Verify Injection
/usr/bin/env bash << 'CONFIG_EOF_2'
doppler run --project aws-credentials --config dev \
--command='echo "KEY: ${#AWS_ACCESS_KEY_ID}; SECRET: ${#AWS_SECRET_ACCESS_KEY}"'
# Expected: KEY: 20; SECRET: 40
CONFIG_EOF_2Step 4: Test AWS Access
doppler run --project aws-credentials --config dev \
--command='aws sts get-caller-identity'
# Expected output: UserId, Account, ArnStep 5: Deactivate Old Key
# In AWS IAM Console:
# Mark old key as Inactive → Wait 24 hours → DeleteAWS Troubleshooting
Issue: 403 Forbidden / InvalidClientTokenId
- Root cause: Credentials expired/rotated elsewhere, or wrong region
- Verify:
doppler run --command='aws sts get-caller-identity' - Check region:
doppler secrets get AWS_DEFAULT_REGION --plain
Issue: Works on One Machine, Not Another
- Root cause: Different Doppler config or HOME variable
- Verify:
doppler me(check logged-in user),echo $HOME
Evolution Log
Convention: Reverse chronological order (newest on top, oldest at bottom). Prepend new entries.
---
2026-02-26: Initial Evolution Log
Status: Skill is in use and maintained. Track improvements here.
Purpose
This evolution log tracks updates to the skill. Each entry should note:
- What changed (content, structure, tooling)
- Why it changed (bug fix, feature request, best practice)
- Files affected
How to Use
1. When updating SKILL.md or references, add an entry here with the date 2. Keep entries reverse-chronological (newest first) 3. Link to ADRs or GitHub issues when relevant 4. Reference specific line changes when helpful
---
Skill: Doppler Credential Workflows
Multi-Service / Multi-Account Patterns
Multiple PyPI Packages
# Package 1
doppler run --project claude-config --config dev \
--command='uv publish --token "$PYPI_TOKEN"'
# Package 2
doppler run --project claude-config --config dev \
--command='uv publish --token "$PYPI_TOKEN_GCD"'Multiple AWS Accounts
# Deploy to staging
doppler run --project aws-credentials --config staging \
--command='aws s3 sync dist/ s3://staging-bucket/'
# Deploy to production
doppler run --project aws-credentials --config prod \
--command='aws s3 sync dist/ s3://prod-bucket/'Skill: Doppler Credential Workflows
Use Case 1: PyPI Package Publishing
Quick Start
# Publish package
doppler run --project claude-config --config dev \
--command='uv publish --token "$PYPI_TOKEN"'Token Setup
Doppler Storage:
- Project:
claude-config - Config:
dev - Secret naming:
PYPI_TOKEN(primary),PYPI_TOKEN_{ABBREV}(additional packages)
Create New Token:
/usr/bin/env bash << 'VALIDATE_EOF'
# Step 1: Create project-scoped token on PyPI
# Go to: https://pypi.org/manage/account/token/
# Select specific project (NOT account-wide)
# Step 2: Store in Doppler (use stdin to avoid escaping)
echo -n 'pypi-AgEI...' | doppler secrets set PYPI_TOKEN_XXX \
--project claude-config --config dev
# Step 3: Verify injection
doppler run --project claude-config --config dev \
--command='echo "Length: ${#PYPI_TOKEN_XXX}"'
# Should show: 220-224 (valid token length)
# Step 4: Test publish
doppler run --project claude-config --config dev \
--command='uv publish --token "$PYPI_TOKEN_XXX"'
VALIDATE_EOFPyPI Troubleshooting
Issue: 403 Forbidden
- Root cause: Token expired/revoked on PyPI
- Solution: Create new project-scoped token, update Doppler
- Verify:
doppler secrets get PYPI_TOKEN --plain | head -c 50(should start withpypi-AgEI)
Issue: Empty Token (Variable Not Expanding)
- Root cause: Not using
--commandflag - ❌ Wrong:
doppler run -- uv publish --token "$VAR" - ✅ Correct:
doppler run --command='uv publish --token "$VAR"'
Issue: Display vs Actual Value
doppler secrets getadds newline to display (formatting only)- Actual value has NO newline when injected
- Verify:
doppler run --command='printf "%s" "$TOKEN" | wc -c'