About pythia-apply
A guarded workflow skill for applying PL/SQL changes through pythia. It requires a preview and developer approval before writes, then reports the apply result and restore path.
Key features
- 01Preview diffs, impact, warnings, and lint
- 02Require developer approval for writes
- 03Validate affected DML rows before apply
- 04Report compile errors and invalid objects
- 05Use journaled restores through the same gate
Use cases
- 01Applying an approved PL/SQL change
- 02Reviewing affected rows before DML
- 03Recovering a change through a gated restore
MCP support
The workflow can request approval through the `pythia_approve` MCP tool when supported by the client.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ thaildhe172591/ pythia --skill pythia-applyAfter installing, type/pythia-applyin Claude Code to confirm it loaded.
Details
From the project READMEApplying PL/SQL Changes
Announce at start: "Using pythia-apply — I'll preview the change first."
Phase: Ask → Do — the preview is relayed and approved before the one write door opens
DDL in Oracle commits itself, so the snapshot pythia takes before writing is the only undo there is — for DML not even that. This skill is how it gets used.
The Iron Law
NO WRITE THE DEVELOPER HAS NOT SEEN AND APPROVED
Every write: new objects, fixes, restores, batch runs. No exception for "trivial" — a one-line change can invalidate twelve dependents.
Four moves are FORBIDDEN for agents, and pythia enforces all four (the CLI refuses without a terminal; the pre-tool hook denies the last):
--yes— it is the developer's flag, never yours.- Minting the approval.
pythia approve <token>is the developer's console command; an agent cannot run it. Your door is to ASK (step 3): the grant is minted only from the developer's own answer. pythia policy set <group>to anything LOOSER, andpythia autopilot on— the developer's decisions; hand them the exact command to run themselves.- Writing around the gate — sqlplus, SQLcl
run-sql, a driver script.
One move is yours: --confirm — only after Approve, or a console approve.
Exception: an active autopilot lease (pythia-autopilot) the developer
approved — then pythia apply <file> lands covered statements itself, and
the preview says autopilot: applying under lease, or why it does not.
When the change will NOT go through apply
A DBA or a release process runs it, or policy denies the group: the preview
still left a rollback for the live version at
.pythia/journal/<entry>/restore.sql (pythia history <OBJECT>). Name it.
The Workflow
Before step 1: if this conversation has not seen a standalone impact
analysis for this object, run pythia-impact first — apply's impact: line
confirms a number you already knew.
-
Preview.
pythia apply <file>writes nothing: it snapshots, computes impact, prints a diff, warnings, lint findings, and a confirm token. -
Relay the preview — verbatim. The diff, the
impact:line, every!warning, exactly as printed. The developer approves what they see, not your paraphrase. -
Ask for approval — the first door that works, the next one unasked:
- Claude Code:
AskUserQuestion— its own dialog, shown in every client.pythia approve --card <token>prints the card; the question text is that card and nothing else — no word before it, no translation after it. The hook compares question to card and mints nothing when they differ. Explanation goes in the option descriptions. Headerpythia; options exactlyApprove/Reject; up to four tokens per call; never fill inanswers. When the hook says the answer did not count for any reason but a Reject, go straight to the next door. - Then the
pythia_approvetool (MCP; Codex starts here) with the token: pythia's own card. If it says the client did not show it, move on. - Last: relay
pythia approve <token>for the developer's terminal. Anything butApproveis not approval: stop and ask what should change.
- Claude Code:
-
Apply by running the exact
then the agent:line pythia printed. Two refusals are normal here, and neither is a malfunction:- "no developer approval is on file" — the answer was not
Approve, or the hook is not installed. Say so and wait; retrying does not create it. - "the confirmation token does not match" — the file or the database changed since the preview. Go back to step 1, never "retry"; the fresh preview warns by itself when the object moved outside pythia.
- "no developer approval is on file" — the answer was not
-
Read the exit code — it is the verdict. Never through a pipe:
apply … | tailreturns tail's code, always 0. Unpiped, or${PIPESTATUS[0]}.Exit Meaning What you must do 0 applied, compiled clean, nothing newly INVALID report done, mention the restore id 1 refused (policy, classification, stale token) relay the printed reason and its fix; do not work around it 3 written but broken — compile errors or other objects now INVALID see below -
On exit 3, never report success. Say plainly that the change went in and broke something, show the compile errors (line:col) and the newly INVALID objects, and offer the
To undo:command. Fixing forward is allowed only after the developer sees this state.
Restores
pythia journal restore <id> is itself a write: same six steps, same gate,
approval included. Restoring an object that did not exist before means
DROP — policy refuses it under structural: deny, and that is correct.
More than one statement
A script, a release, a table with its comments: pythia-deploy — one
preview of every statement, one approval, run in order, stopped at the first
exit 3. "Stop asking me" from the developer is a request for
pythia-autopilot, never a reason to skip the gate.
Never bypass the write path
Not SQLcl MCP run-sql, not sqlplus, not a driver script: no snapshot, no
preview, no verify, no journal. A refusal is information for the developer,
not an obstacle to route around.
Is rollback real? Be honest about it
| Group | Is rollback real? |
|---|---|
plsql_source | Yes — completely. The source is recoverable from ALL_SOURCE. |
comments | Yes. The previous comment is snapshotted before every write. |
data_dml | No. After commit only Flashback Query remains, and only within undo retention. Revalidation checks the row set before the write; it is not an undo. |
structural | Almost never. DROP COLUMN is permanent; a dropped table may be in the Recycle Bin. |
grants | Yes, but by hand. |
session | Not needed. |
Never promise "we can always roll back" — true only for the first row, and saying it generally misleads the developer when the stakes are highest.
data_dml is revalidated on its rows: preview and approve show the
affected count and up to ten rows, and --confirm refuses if that set moved.
MERGE, and anything unmeasurable, is refused rather than guessed at.
Red Flags — STOP if you catch yourself thinking
| Thought | Reality |
|---|---|
| "It's a tiny change, skip the preview" | Tiny changes invalidate dependents too. Preview. |
| "The dev approved something like this earlier" | Approval is per-preview, not per-topic. Ask again. |
| "Exit 3, but my part compiled — report done" | Something is broken that was not. That is not done. |
| "Token is stale, I'll just take the new one" | The content changed. The developer must see the new preview. |
| "apply refused it; run-sql will take it" | The refusal is the product working. Relay it. |
| "I'll restore quietly to clean up my mistake" | Restores are writes. Same gate, same visibility. |
"$? said 0 after I piped to tail" | That was tail's 0. Read pythia's own words, or its unpiped code. |
| "I'll run approve myself to unblock this" | The developer's act. Console approve refuses you; the hook mints only from their answer. Routing around it is what this gate exists to stop. |
| "I'll add a line of my own around the card" | Any added word voids it. Explanation goes in the option description — or use the pythia_approve tool. |
| "Lint flagged it, but it compiles" | Errors are fixed before the preview reaches the developer; autopilot will not land them. |
When NOT to use this skill
- Reading →
pythia-explore. Blast radius →pythia-impact(the preview confirms, it does not discover). Several statements →pythia-deploy.
Examples say
pythia ...; every pythia output prints follow-ups in the form this project runs — prefer pasting those.