Skip to content

Limitations

What the plugin cannot do, or does differently from the platform, and why. Each entry names the recipe that works around it where one exists.

Reading results

  • Logs come from Python script, wheel and JAR tasks only, and hold the last 5 MB. Notebook tasks have no logs; what a notebook printed is in task.notebook.stdout, read from the notebook that jobs.export_run renders, by default only for notebook tasks that ran and did not succeed, and only for the last attempt of a retried task. That export format is not documented, so it is parsed best effort: when it cannot be read, cells is empty, html keeps the export and a UserWarning is emitted. Printed Python output was checked on serverless compute and on classic job clusters running 17.3 LTS and 13.3 LTS; Scala, R and %sh cells were not. Databricks documents that on serverless compute the logs of one task can include output from other tasks in the same run; in the plugin's tests each task's logs held only its own output, but this is not guaranteed.
  • Notebook output keeps only the first 5 MB. A truncated output or log emits a UserWarning naming the task, and output_json refuses truncated output with a ValueError.
  • Cancelled tasks have no output. A task that ended CANCELED, such as the tasks of a run stopped by stop_after, counts as not run: the plugin reads no output, logs, error or notebook for it. Assert on the data the job wrote instead.
  • Resolved task parameters cost one request per task. The Jobs API fills a task's resolved_values only when the task run is fetched by its own id; on the job run the field is always null. A task run that cannot be read leaves TaskRun.parameters as None with a warning.
  • SQL rows come from SQL file tasks only. TaskRun.sql_result is read from sql_output.file_output, a field of runs/get-output that the Jobs API reference does not document and databricks-sdk does not model: one extra request per SQL file task that ran. A saved query task reports only query_text, warehouse_id and a link to the result page (checked October 2026), so it gives None unless saved_queries="inline" runs its text as a SQL file task, which runs as the job's identity rather than the query's owner and without the SQL editor's automatic LIMIT 1000. Alert and dashboard tasks give None. Values are strings, a NULL is None, and the platform keeps at most 1,000 rows for 7 days.
  • Task values need probes. Task values are not available through the Jobs API, so they are read by probe tasks, each a serverless task start of 7 to 30 seconds, even when its task did not run. For the same reason upstream_from and replay can only replay a run that captured its values with capture_task_values, never a production run.
  • dbt artefacts are a link that expires. TaskRun.dbt_output.artifacts_link is a pre-signed URL that the platform signs for 30 minutes. With Lakeflow(dbt_artifacts=True) the plugin downloads it while building the run, so TaskRun.dbt is only filled for runs that run() or wait() built, and a tarball over 50 MB is not downloaded. report.json keeps the link and leaves out the headers.
  • The pipeline update is found by time. The Jobs API does not say which update a pipeline task started, so TaskRun.pipeline_update is the update created between the task's start and end, one started by a job task first. Two pipeline tasks that update the same pipeline in one run, or anyone else updating the deployed pipeline while the task runs, can make the match ambiguous.
  • Alert tasks succeed whether or not the alert triggered. Assert on TaskRun.alert_state. Only an alert that cannot be evaluated fails the task, with alert_state ERROR, and the Jobs API reports no error for it, so TaskRun.error holds the termination message.

What the copy changes

  • Job code must read catalog and schema from job parameters. Bundle variables are fixed at deploy time, and the plugin deploys each target once per session for each set of variables.
  • Run Job tasks run the deployed child job. The copy keeps run_job_task.job_id as deployed, so a test of a parent job starts a real run of the child, with the child's run_as and notifications, which is not cancelled or deleted with the copy. The plugin warns, or refuses with children="refuse". Copying or stubbing the child is not supported.
  • Alert tasks and Power BI tasks run as the template has them. An alert task notifies its subscribers and has no pause flag; a Power BI task updates the semantic model whether or not refresh_after_update is set. Disable them with disable=.
  • Pipeline tasks run the deployed pipeline unless you copy it. By default every test that runs a pipeline task updates the same deployed pipeline and its tables. pipelines="copy" and lakeflow.pipeline() run copies instead.
  • A pipeline copy only moves the tables its code names without a schema. A fully qualified name such as prod.sales.orders still writes where it says. The copy runs the code the last bundle deploy uploaded, as you, in development mode, without the deployed pipeline's event log. A pipeline that publishes to the Hive metastore cannot be copied, and pipelines/get returns no parameters, so a copy carries none; set what the code reads with configuration=. On databricks-sdk 0.100 to 0.109, pipelines.delete takes no cascade, so a deleted copy keeps its tables until its schema is dropped.
  • Stubbed outputs are literals. A stub cannot run a query or evaluate an alert, so a Stub's outputs replace {{tasks.<key>.output.<path>}} references with fixed strings, in the platform's format: rows is a JSON array of row objects and first_row one such object, every value a string and NULL as an empty string. Code that reads the task's output with dbutils rather than a reference is not affected.
  • Setup and teardown functions ship as source. They run as notebooks on the job's compute, so they must be self-contained. Lambdas, closures, decorated functions and functions defined in a REPL are refused. On a helper cluster older than Databricks Runtime 13.3 LTS, which has no dbutils.widgets.getAll(), params holds only the declared parameters and the keys of job(params=...). Each costs one task start per run.
  • Single-task jobs keep only the target task. On a shared job cluster a task can import libraries that other tasks declared, and on its own it loses them. Declare every library on the task that needs it.
  • Test-only dependencies skip serverless notebook tasks without an environment, which cannot take dependencies from the job settings; the plugin warns. A local wheel with extras cannot be installed as a cluster library, so it raises LakeflowError when the copy has a Python task on a cluster.
  • Serverless auto-retry is off by default in every copy, so a test sees one attempt. Pass disable_auto_optimization=False to keep the platform's behaviour, or set max_retries with task_overrides to test retries.
  • Forced conditions apply to the whole copy. Every run of a copy made with force_conditions takes the same branch; make another copy for the other branch. Condition tasks nested in a for-each task cannot be forced, and forcing a condition task that disable disables, directly or downstream, raises LakeflowError.
  • For-each tasks have limits of their own. An iteration cannot set task values, so capture_task_values refuses a for-each task and its nested task, and its input is reported only where the nested task passes {{input}}. Iterations are not exported to notebook. The nested task cannot be the target of run(only=...). for_each_inputs works on top-level for-each tasks, as a literal list of at most 5,000 characters as JSON, of all primitives or all objects. A lifted body sees its input as __pytest_input job parameters, written as an iteration would write them, and may set task values, which an iteration cannot.
  • Only trigger references in the settings can be simulated. trigger= rewrites {{job.trigger.<path>}} references, so code that reads the trigger context another way still sees a run-now run, and run.trigger_type stays ONE_TIME. A list value such as table_update.updated_tables is passed as the string you give, because the platform's format for it has not been recorded on a triggered run. A job whose settings use table_update or file_arrival references cannot be copied without trigger setting them.
  • dbt_commands needs a job without job parameters. The Jobs API refuses the legacy run parameter on a job that has job parameters, which test_marker, trigger= and input= declare. Use task_params instead.
  • single_node needs dedicated access mode. It sets the explicit single node fields (num_workers, spark_conf and the ResourceClass tag), not kind and is_single_node. The Clusters API accepted them on a SINGLE_USER job cluster without a policy; a cluster policy that forbids spark.databricks.cluster.profile would refuse them. Standard (shared) access mode clusters were not tested.
  • Debug notebooks replay parameters only. They set the task's resolved parameters as widgets and %run the task's notebook; they do not restore task values or the Spark session. A text widget holds at most 2048 bytes. Notebook tasks inside a for-each task are not covered.
  • Names starting with __pytest_ are reserved for the plugin's tasks, environments, widgets and parameters. Tests cannot pass them as params or in task_params.

Deployments and sessions

  • The Databricks CLI is required. Deploying is only possible through the CLI, so the plugin shells out to it. There is no pure-SDK mode.
  • Concurrent sessions sharing one target replace each other's wheels. A deploy from one session replaces the wheels that another session's jobs are still running. deploy_flags=["--fail-on-active-runs"] does not prevent it, because it only sees runs of the deployed jobs. Use separate deployments per branch and identity.
  • Different setups of one target can collide under xdist. Lakeflow instances for the same target that differ in variables, sync, deploy_flags or helpers_path are set up separately, so two workers can deploy the target at the same time and one can fail on the CLI's deployment lock. Give each set of variables its own target.
  • Serverless may run a cached wheel with the same file name. A wheel artifact without dynamic_version: true is uploaded under the same file name on every deploy while its version is unchanged, and databricks/cli#1621 and databricks/cli#2958 report serverless running the cached old build. A probe in the maintainers' test workspace in October 2026 did not reproduce it. The plugin warns once per session, or raises with strict_artifacts=True. Set dynamic_version: true on the artifact.
  • With sync="none", a stale wheel is caught by its modification time only. The plugin warns when a local library file is newer than its uploaded copy by more than 60 seconds, which allows for clock skew. It misses a wheel deployed from another checkout, an older build copied over a newer one, and code changed but not rebuilt. It covers the local libraries of the bundle's jobs, not local paths in extra_dependencies.
  • sync and none read bundle validate output, which leaves library paths local and ${resources...} references unresolved. Jobs affected by that are refused, with an error that says to use deploy.
  • One run at a time per copy. Each copy is created with max_concurrent_runs: 1. Parallelism comes from separate tests, each with its own job, or from several copies in one test started with start().
  • Lakeflow works only inside pytest. It raises LakeflowError outside a pytest session and outside a test, because jobs are tied to the running test for naming, tagging and teardown. Lakeflow.sweep() is the exception: it runs from any script.