Best Practices

Practical guidance for getting reliable, cost-effective results out of the 3XCode CLI, grounded in how the conversion pipeline actually behaves.

Organize with Workspaces

A workspace is the unit of organization for a migration: it owns its own input/, output/, .sessions/, and logs/ directories. Create one workspace per logical migration (a schema, a domain, a client engagement) rather than dumping unrelated SQL into a single catch-all workspace. This keeps session history, resumability, and audit reports scoped to work that actually belongs together.

One Workspace per Migration

Related views, procedures, and functions that reference each other belong in the same workspace so dependency resolution and session history stay meaningful.

Prune Stale Entries

Run `workspace prune` periodically to clean up registry entries pointing at directories that no longer exist, keeping `workspace list` and `workspace switch` accurate.

Set up a dedicated workspace
# Initialize a workspace for this migration
3xcode workspace init ./billing-migration --name "billing-migration"

# Check which workspace is currently active
3xcode workspace status

# List all registered workspaces (active one marked with ●)
3xcode workspace list

# Switch between workspaces for different migrations
3xcode workspace switch ./billing-migration

Dependency-Aware Bulk Conversion

When SQL objects reference each other (a stored procedure that selects from a view, a function called by three other procedures), don't convert files one at a time in isolation. Use bulk conversion over the whole directory so the pipeline's planning phase can build the full cross-object dependency graph and topologically sort the work before any code is generated.

The planning phase orders conversion by precedence: views and standalone functions first (no dependencies), then any remaining functions, then procedures, then triggers. This way, by the time a dependent object is converted, the real PySpark signature of what it calls already exists rather than being guessed. This is the same reason conversion itself runs in dependency tiers (utilities → standalone → main pipeline): later tiers see the real signatures produced by earlier ones. Converting interrelated objects file-by-file instead of as a batch throws this ordering away.

--schedule Controls Ordering

`simple-first` (default) surfaces quick wins early; `complex-first` front-loads the highest-complexity objects if you'd rather find problems immediately.

Name Your Session

Pass `--session` with a descriptive name so a bulk run is easy to find later in `session list` and easy to resume if interrupted.

Bulk-convert a directory of interrelated SQL
# Convert every .sql file in a directory as one dependency-ordered batch
3xcode pyspark convert --dir ./sql/ --session "billing-migration" -w 4

# Prefer to see complex objects converted first
3xcode pyspark convert --dir ./sql/ --schedule complex-first --session "billing-migration"

# Resume if a bulk run was interrupted: first find the actual session ID
# (auto-generated, not the bare --session name you passed above)
3xcode session list

# Then resume using the ID shown in the listing
3xcode pyspark convert --resume <session-id-from-list>

Start with --dry-run

Before committing to a full conversion pass (especially on a large or unfamiliar batch of SQL), run --dry-run first. It executes only the discovery and planning phases: dialect detection, platform fingerprinting, the full object inventory, complexity scoring, and the dependency-ordered conversion plan, with no PySpark code generated. pyspark analyze does the same thing for a single file.

This gives you a cheap first look at what the pipeline thinks it's dealing with: detected dialect and source platform, object count and complexity levels, known limitations, and the planned conversion order, before spending conversion-phase budget on objects that may have been misclassified or that you'd rather exclude.

Preview before converting
# Dry-run a bulk batch: discovery + planning only, no conversion
3xcode pyspark convert --dir ./sql/ --dry-run

# Analyze a single file the same way
3xcode pyspark analyze legacy_proc.sql

Note that discovery and planning still require login and a valid license: analysis is not a free or offline operation, since it makes real LLM calls. The savings come from skipping the conversion, validation, and auto-fix phases, not from skipping the model entirely.

Review Validation & Audit Reports Before Trusting Output

Every conversion produces a <stem>_audit.md (human-readable) and <stem>_audit.json (canonical) report alongside the generated .py files. Treat these as the first thing you read, not the code. Each output file gets a per-file completion and confidence score, and the effective letter grade (A+ through F) is the lower of the two, so a file can't look better than its weakest signal.

Developer action item buckets

BucketWhat it means
auto_fixedThe auto-fix phase found and safely corrected the issue (still worth a spot check).
requires_manualCursors, dynamic SQL, MERGE/upsert logic: patterns with no safe automatic transform. Comes with prescriptive `how_to_fix` guidance.
infrastructure_setupNeeds external provisioning, not code changes: Delta Lake tables, Change Data Feed, JDBC connections, streaming checkpoints.
recommended_reviewLower-severity findings surfaced for awareness, never auto-fixed.

Auto-fix only ever writes a corrected file after two guardrails pass: the fixed content isn't more than 50% shorter than the original, and it parses as valid Python. If either check fails, the fix is reverted and the original file is left untouched, with the reason recorded in the audit. That's a safety net against a bad rewrite, not a guarantee the accepted fix is semantically correct: read the requires_manual and recommended_review sections of the audit for every file before treating a migration as done.

Keep Worker Counts Conservative

The -w / --workers flag controls how many files convert in parallel during a bulk run. 0 (the default) auto-detects a recommended count from available CPU and memory, the same detection surfaced by 3xcode health. Each worker is a full concurrent AI-driven conversion session, and pushing this too high doesn't just consume local resources, it multiplies concurrent LLM calls and API cost bursts across the whole batch at once.

Check the Recommendation First

`3xcode health` reports CPU count, available/total RAM, and a recommended worker count for your machine before you set -w explicitly.

Detached Runs Are Capped Too

Background workers started with `--detach` are capped machine-wide at 3 concurrent sessions, a hard ceiling regardless of workspace.

Check recommended workers, then convert
# System Resources check shows a recommended worker count
3xcode health

# Let the CLI auto-detect (default, -w 0)
3xcode pyspark convert --dir ./sql/

# Or set an explicit, conservative count
3xcode pyspark convert --dir ./sql/ -w 4

Use BYOK for Cost Control on Large Migrations

Every plan tier has a monthly conversion cap: Free: 5, Pro: 200, Team: 1,000, Enterprise: unlimited. For a large migration that will blow past a per-tier monthly limit, or where you simply want conversion cost billed directly to your own provider account, Bring Your Own Key (BYOK) routes conversion calls straight to Anthropic, AWS Bedrock, Google Vertex AI, or Azure AI Foundry using your own credentials instead of the platform-provided key.

Keys are stored only locally (OS keychain, with a permissions-locked file fallback) and are never sent to the 3XDE backend; only the provider name and your preferred models are registered server-side. BYOK access is admin-gated, so it needs to be enabled on your account before keys store will complete the flow.

Set up BYOK
# Request BYOK access if it isn't enabled on your account yet
3xcode contact-admin -m "Please enable BYOK for my account"

# Store credentials for your provider of choice
3xcode keys store --provider anthropic
# (also supports: bedrock, vertex, azure)

# Verify the key actually works before a large run
3xcode keys test --provider anthropic

# Confirm which key source is currently active
3xcode keys status

Verify Spark SQL Passthrough Decisions

When discovery classifies a file's dialect as spark_sql (genuine Databricks SQL or Hive), it's already running on the same engine as the PySpark target. There's no correctness gap to close: the conversion decision is about idiom and readability, not semantics, and the pipeline may reasonably choose to keep a block as a spark.sql("...") passthrough rather than rewriting it into the DataFrame API.

Don't accept either direction blindly

A passthrough decision trades DataFrame-API readability and type-safety for a smaller diff from the source. A DataFrame rewrite trades a larger diff for more idiomatic, testable PySpark. Neither is universally correct: review which one the agent chose for each spark_sql object and confirm it matches your team's actual style and maintainability preferences, rather than assuming the rewrite is automatically the better outcome.

Because there's no dialect-translation risk on these files, validation and auto-fix still run and are still worth reading, but treat the audit's findings here as style/maintainability feedback on a same-engine rewrite, not as a correctness gate the way it is for a tsql or plsql conversion.

Quick Reference Checklist

Before You Start

Dedicated workspace initialized. BYOK configured if this migration will exceed your tier's monthly conversion cap.

Before You Convert

Ran --dry-run or pyspark analyze on the batch. Checked `3xcode health` for a recommended worker count.

For Interrelated Objects

Used --dir bulk conversion (not one-file-at-a-time) so dependency ordering applies across views, functions, procedures, and triggers.

Before You Ship

Read every _audit.md, resolved requires_manual items, and reviewed spark_sql passthrough vs. rewrite choices.

PracticeCommand
Scope a migration to its own workspace3xcode workspace init ./path --name "migration-name"
Preview before spending on full conversion3xcode pyspark convert --dir ./sql/ --dry-run
Check a sane worker count for this machine3xcode health
Convert interrelated objects as one batch3xcode pyspark convert --dir ./sql/ -w 4 --session name
Route large migrations to your own provider key3xcode keys store --provider anthropic
Inspect what needs manual attentionRead `<stem>_audit.md` before merging output