The concept
One-way doors
Decisions that create gravity. Once traffic, users, or other code depends on them, changing course gets expensive.
- Database schema after launch
- API contract with external consumers
- Auth model shaping permissions
- Infrastructure stack your team learns
Two-way doors
Decisions you can reverse easily. Walk through, and if it's wrong, walk back. No lasting consequences.
- UI components and styling
- Utility functions and helpers
- Test infrastructure
- Documentation and logging
This skill teaches Claude to recognize the difference and pause before one-way doors. The companion hook automates enforcement by blocking file creation for known architectural patterns until you've discussed the trade-offs.
What gets flagged
Data models
Schemas, migrations, entity definitions. Once your database has rows, every change requires a migration.
Infrastructure
Docker, Terraform, Kubernetes, Helm. Infrastructure choices constrain everything built on top.
Auth boundaries
Security rules, RBAC, permissions. Auth boundaries are load-bearing walls in your architecture.
API contracts
OpenAPI specs, protobuf definitions, route files. Published APIs are promises to consumers.
Event systems
Pub/sub, message queues, event buses. Event schemas are contracts between producers and consumers.
CI/CD pipelines
GitHub Actions, GitLab CI, Jenkins. Pipelines become the backbone of your release process.
Dependencies
package.json, Cargo.toml, go.mod. Framework choices ripple through your entire codebase.
Cloud configs
Firebase, Firestore indexes. Cloud service configs lock you into specific providers and architectures.
How it works
Hook intercepts file creation
The PreToolUse hook fires on every Write call. It extracts the file path and checks it against known one-way-door filename patterns.
One-way door detected
If the file matches a one-way-door pattern and isn't already approved this session, the hook records it as pending, exits with code 2 (block), and sends a message to stderr explaining what was caught and why.
Claude discusses trade-offs
Claude uses AskUserQuestion to present the architectural decision: what it does, alternative approaches, and trade-offs. The user decides how to proceed.
Approve, then write with confidence
When the user answers, a companion hook (PostToolUse:AskUserQuestion) promotes the file to approved for the session, so Claude's retried write passes instead of re-blocking. Two-way door files always pass through silently, and a new session starts with a clean slate.
The hook scripts
Two shell scripts that share a session-scoped approval ledger. The check runs on every Write: it pattern-matches the filename and blocks with exit code 2 if the file is a one-way door it hasn't already seen approved this session.
#!/bin/sh
INPUT=$(cat)
[ -z "$INPUT" ] && exit 0
FILE_PATH=$(echo "$INPUT" | grep -oP '"file_path"\s*:\s*"[^"]*"' \
| head -1 | sed 's/.*"file_path"\s*:\s*"//;s/"//')
[ -z "$FILE_PATH" ] && exit 0
# Session-scoped approval ledger
SESSION_ID=$(echo "$INPUT" | grep -oP '"session_id"\s*:\s*"[^"]*"' \
| head -1 | sed 's/.*"session_id"\s*:\s*"//;s/"//')
[ -z "$SESSION_ID" ] && SESSION_ID="default"
STATE_DIR="$HOME/.claude/hooks/state/one-way-door"; mkdir -p "$STATE_DIR"
APPROVED_FILE="$STATE_DIR/$SESSION_ID.approved"
PENDING_FILE="$STATE_DIR/$SESSION_ID.pending"
# Already approved this session: note it and allow
if [ -f "$APPROVED_FILE" ] && grep -Fxq "$FILE_PATH" "$APPROVED_FILE"; then
echo "one-way-door: proceeding with previously-approved $(basename "$FILE_PATH")" >&2
exit 0
fi
FILENAME=$(basename "$FILE_PATH")
FILENAME_LOWER=$(echo "$FILENAME" | tr "[:upper:]" "[:lower:]")
FILE_PATH_LOWER=$(echo "$FILE_PATH" | tr "[:upper:]" "[:lower:]")
DIR=$(dirname "$FILE_PATH")
# Early-exit safelist: always-reversible classes pass even if the name
# contains a keyword like "auth" (tests, fixtures/mocks, markdown, docs)
if echo "$FILENAME_LOWER" | grep -qE \
"^test_.*\.py$|_test\.py$|\.test\.(ts|tsx|js|jsx)$|\.spec\.(ts|tsx|js|jsx)$"; then
exit 0
fi
if echo "$FILE_PATH_LOWER" | grep -qE \
"/tests?/|/__tests__/|/fixtures?/|/mocks?/|/__mocks__/"; then
exit 0
fi
if echo "$FILENAME_LOWER" | grep -qE "\.md$"; then exit 0; fi
if echo "$FILENAME_LOWER" | grep -qE "\.txt$|\.rst$"; then
echo "$DIR" | grep -qE "plans?|docs?|notes?|superpowers" && exit 0
fi
ONE_WAY=0
REASON=""
# Data models, infra, auth, APIs, events, deps, cloud, CI/CD
if echo "$FILENAME_LOWER" | grep -qE \
"schema\.(prisma|graphql|sql)|migration|\.sql$|models?\.(py|ts|js)$"; then
ONE_WAY=1; REASON="data model / database schema"
fi
if echo "$FILENAME_LOWER" | grep -qE \
"^(docker-compose|dockerfile|terraform|pulumi|cdk)|\.tf$"; then
ONE_WAY=1; REASON="infrastructure config"
fi
if echo "$FILENAME_LOWER" | grep -qE \
"auth\.(ts|js|py)|\.rules$|security\.(ts|js|py|json|rules|yaml|yml)|rbac\.(ts|js|py|json)|permissions\.(ts|js|py|json)"; then
ONE_WAY=1; REASON="auth / security rules"
fi
# ... (8 categories total)
if [ "$ONE_WAY" = "1" ]; then
grep -Fxq "$FILE_PATH" "$PENDING_FILE" 2>/dev/null \
|| printf '%s\n' "$FILE_PATH" >> "$PENDING_FILE" # record as pending
echo "ONE_WAY_DOOR: $FILENAME ($REASON)" >&2
echo "Blocked. Use AskUserQuestion, then retry." >&2
exit 2 # Block the write
fi
exit 0 # Allow two-way doors
A second hook runs on PostToolUse:AskUserQuestion. When the user answers any AskUserQuestion, it promotes every pending path to approved so the retried write passes. It never blocks. It keys on the event, not the specific question — so an unrelated question answered while a file is pending approves it too. The block-then-discuss prompt is the guardrail; the ledger just stops an already-discussed file from re-blocking.
#!/bin/sh
# one-way-door-approve.sh (PostToolUse:AskUserQuestion)
INPUT=$(cat); [ -z "$INPUT" ] && exit 0
SESSION_ID=$(echo "$INPUT" | grep -oP '"session_id"\s*:\s*"[^"]*"' \
| head -1 | sed 's/.*"session_id"\s*:\s*"//;s/"//')
[ -z "$SESSION_ID" ] && SESSION_ID="default"
STATE_DIR="$HOME/.claude/hooks/state/one-way-door"; mkdir -p "$STATE_DIR"
APPROVED_FILE="$STATE_DIR/$SESSION_ID.approved"
PENDING_FILE="$STATE_DIR/$SESSION_ID.pending"
[ -s "$PENDING_FILE" ] || exit 0 # nothing pending
# Promote each pending path into approved, deduped
while IFS= read -r path; do
[ -z "$path" ] && continue
grep -Fxq "$path" "$APPROVED_FILE" 2>/dev/null \
|| printf '%s\n' "$path" >> "$APPROVED_FILE"
done < "$PENDING_FILE"
: > "$PENDING_FILE" # clear pending now that it's approved
exit 0
Full check script with all 8 categories, plus the approval hook, in the skill directory.
Two-way doors
These file types pass through the hook silently. They're safe to decide quickly and change later. Test files, Markdown, and docs/plans text files are enforced by an early-exit safelist, so they pass even when the name contains a keyword like "auth".
UI components
Utilities
Test files
Documentation
Styling / CSS
Logging
Static assets
App config
Installation
Option 1: Install the skill
# Recommended: install the dev-toolkit plugin
/plugin marketplace add jamditis/claude-skills-journalism
/plugin install dev-toolkit@claude-skills-journalism
# Or copy just this skill from the plugin tree
git clone https://github.com/jamditis/claude-skills-journalism.git
cp -r claude-skills-journalism/dev-toolkit/skills/one-way-door ~/.claude/skills/
Option 2: Add the CLAUDE.md rule
Add this paragraph to your project's CLAUDE.md for prompt-based enforcement (no hook needed):
### One-way door check
Before creating new files that represent architectural
decisions, ask: "Which of these decisions would be
difficult to reverse?" One-way doors include data models,
service communication patterns, auth boundaries, tenancy
models, and infrastructure configs. If a decision is a
one-way door, pause and discuss the trade-offs before
committing.
Option 3: Add the automated hooks
For automated enforcement, save both scripts and add this to your settings.json. The approval hook is required — without it, the check re-blocks an approved file on every retry:
{
"hooks": {
"PreToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "/path/to/one-way-door-check.sh"
}]
}],
"PostToolUse": [{
"matcher": "AskUserQuestion",
"hooks": [{
"type": "command",
"command": "/path/to/one-way-door-approve.sh"
}]
}]
}
}
Windows (PowerShell)
On Windows, hooks run through PowerShell and file paths can use backslashes. Behavior-matched ports — one-way-door-check.ps1 and one-way-door-approve.ps1 — ship in the skill directory and use the same session ledger, safelist, and categories. Copy both into your hooks folder (for example %USERPROFILE%\.claude\hooks\), then wire them with the PowerShell launcher — update the paths to match where you saved them (replace <you>):
{
"hooks": {
"PreToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "powershell -ExecutionPolicy Bypass -File C:/Users/<you>/.claude/hooks/one-way-door-check.ps1"
}]
}],
"PostToolUse": [{
"matcher": "AskUserQuestion",
"hooks": [{
"type": "command",
"command": "powershell -ExecutionPolicy Bypass -File C:/Users/<you>/.claude/hooks/one-way-door-approve.ps1"
}]
}]
}
}
Or browse this skill in the GitHub repository.
Related skills
Measure twice, cut once
Pause before irreversible decisions. A five-minute conversation about trade-offs saves weeks of migration pain.
View on GitHub