Remove every backend that has never been run against real hardware - #149
Merged
Conversation
Checkpoint of two parallel removal agents that were stopped mid-flight so
the machine could be suspended. The tree in this commit DOES NOT IMPORT:
ModuleNotFoundError: No module named 'clustrix.executor_kubernetes'
clustrix/executor.py still imports KubernetesJobManager, and utils.py still
has the PBS/SGE script generators, because the code agent was stopped just
as it reached utils.py. Committed only so the work survives the suspend.
Done so far: 95 files deleted (cloud_providers/, cost_providers/,
pricing_clients/, kubernetes/, executor_cloud.py, executor_kubernetes.py,
cloud_provider_manager.py, cost_monitoring.py, auto_install.py, 9 notebooks,
kubernetes/pbs tutorials, cost_monitoring API page); 7 files partially
edited.
--no-verify is deliberate: the pre-commit hook runs black/flake8/mypy, and
a tree that cannot import cannot pass them. The next commit on this branch
must pass the full gate.
Resume instructions: notes/2026-08-19-backend-removal-session.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
Records that 07db2e6 is a non-importable WIP tip, exactly what remains in each lane, and the four-check gate the next commit has to pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
The master Tests run was still in flight at suspend, and an older Real World Tests failure on 0ca28fa has not been examined. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
Pushing the WIP branch surfaced it: four real-world categories reported failure and the hook announced success. run_real_world_tests.py drops every result and exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
…sources Every remaining reference to a removed backend in docs/source now points at the :ref:`removed-backends` note in limitations.rst, which names the tracking issues (#140-#146) and says the backends are planned for a future release rather than currently supported. Also drops the broken :doc:`cost_monitoring` cross-references left behind by the deletion of docs/source/api/cost_monitoring.rst.
Drops the Kubernetes/AWS/Azure/GCP/Lambda UI sections, credential fields, region/instance-type population and connectivity tests, the cost-monitoring checkbox, and the pbs/sge branches. The cluster-type dropdown now offers exactly local, ssh, slurm and huggingface, and the HuggingFace section targets HF Jobs rather than Spaces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
Drops the k8s_* controls, their BACKEND_ONLY_FIELDS entry and their WIDGET_MANAGED_FIELDS entries, and narrows the pbs/sge conditionals to ssh/slurm. The cluster-type dropdown is driven by SUPPORTED_CLUSTER_TYPES, so it follows config.py without a change here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
The package imports again. Removed from the modules this commit owns:
- executor.py: the KubernetesJobManager and CloudJobManager re-exports,
which pointed at modules already deleted.
- config.py: every k8s_*, aws_*, azure_*, gcp_*, lambda_*, cloud_* and
cost_monitoring field (79 lines); the auto_install cloud-dependency
hook in __post_init__ and in configure(), along with configure()'s
auto_install_deps parameter.
- config.py: SUPPORTED_CLUSTER_TYPES is now ("local", "ssh", "slurm",
"huggingface").
- decorator.py: the provider/instance_type/region parameters and the
seven Kubernetes auto-provisioning ones, the k8s readiness branches in
both the sync and async paths, and the k8s auto-provisioning check in
_choose_execution_mode. The kwargs passthrough list keeps only the
names a surviving backend reads.
- utils.py: _create_pbs_script and _create_sge_script and their dispatch;
normalize_memory's kubernetes/pbs/sge targets.
- cli_credentials.py: the AWS, Azure, GCP, Kubernetes and Lambda Cloud
collectors and validators, and their entries in the setup wizard and
the credential test loop (883 -> 497 lines).
Two things deliberately kept:
hf_hardware, hf_username and hf_sdk look like HuggingFace *Spaces* fields
and sit under a comment that said so, but hf_jobs.py reads them as
fallbacks for hf_flavor and hf_namespace. They stay; the comment is
corrected.
New in config.py: REMOVED_CLUSTER_TYPES and _REMOVED_SETTINGS, checked in
load_config before the difflib path. Without them an existing clustrix.yml
carrying k8s_namespace gets "did you mean ...?" pointed at an unrelated
field, and cluster_type: pbs gets no explanation at all. Now each names
the backend and its tracking issue (#140-#146).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
Rewrites the PBS/SGE/Kubernetes/cloud config examples in complete_api_demo and cluster_config_example, drops the cost-monitoring example from basic_usage, fixes the dead links to the deleted pbs/kubernetes/cost_monitoring notebooks, and deletes the now-unreferenced widget_gcp.png / widget_lambda.png screenshots.
main() dropped every runner.run_*_tests() return value and never called sys.exit, so the script exited 0 no matter what. The pre-push hook guards each category with `if ! python scripts/run_real_world_tests.py --<cat>`, which therefore could never fire: four categories printed "failed" and the hook still announced "All real-world tests passed!" and allowed the push. Same class as the flake8 --exit-zero and mypy continue-on-error steps fixed in #138 -- a check that reports problems but cannot fail. Also fixes the second half of #147: the failure message printed only result.stdout, which was empty in all four observed failures because a pytest collection error goes to stderr. _report_failure now prints the exit code, stdout, stderr, and says so explicitly when there was no output at all. Verified by appending a deliberately failing test to tests/real_world/test_filesystem_real.py and running the script: EXIT CODE: 1 ❌ Filesystem tests failed (exit 1) assert False, "deliberate failure to verify exit-code propagation" E AssertionError: deliberate failure to verify exit-code propagation and, with that test removed, the same command exits 0. Before this change the failing case also exited 0 with an empty message body. Also drops the removed backends from two scripts: the kubernetes tutorial entry in check_docs_examples' _SECTION_BOUNDS (that page is deleted) and the aws/azure/gcp/lambda_cloud entries in the credential display names. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
…loud) Deleted test modules that exist solely to exercise backends removed from the package: Kubernetes, PBS, SGE, Lambda Cloud, direct cloud compute and container-registry validators, plus the reference kubernetes workflow. tests/integration lost its eight Kubernetes auto-provisioning scripts.
Its dropdown carried a third hardcoded copy of the backend list, offering pbs, sge, kubernetes, aws, azure and gcp -- none of which the executor can dispatch any more. Reading the constant is what stops the copies drifting; that is why the constant exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
…CHANGELOG README and CLAUDE.md now carry a single 'not currently supported' note next to the supported-backends table, naming tracking issues #140-#146 and saying the backends are planned for a future update without promising a date. The Cloud Providers and Cost Monitoring sections are replaced by that note. CHANGELOG records both removals under 0.2.0 as BREAKING, including the five cost-monitoring functions that no longer exist. The 'Implemented but unverified' section is gone: those backends no longer exist in the code. docs/aws/ is kept -- scripts/aws/ cleanup tooling still needs those IAM permissions -- with a banner marking each guide historical.
cluster_job_validator: ClusterType now only SLURM and SSH; the qstat and kubectl branches are gone. test_cluster_job_system: the required-test-file list no longer asserts the existence of the deleted pbs/sge/kubernetes submission tests. run_cluster_job_tests: availability probe and --cluster choices reduced to slurm/ssh. test_advanced_schedulers_comprehensive: PBS and SGE submission tests removed, remaining tests are SLURM-only.
Removing the PBS/SGE/Kubernetes/cloud blocks left stranded blank runs (E303, W391) in the three executor modules and an unused `import sys` in cli_credentials. black and flake8 are clean on these four files now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
docker-compose no longer starts a kind control plane; setup_test_infrastructure drops the kind/kubectl dependency checks, the Kind cluster config and RBAC bootstrap, the kubernetes block in test_infrastructure.json, the KUBECONFIG export and the Kind teardown.
Deletes tests/unit/test_backends_placeholder_hosts.py (Azure/GCP/Lambda providers are gone), the PBS and SGE job-script tests in test_utils.py, and the PBS/SGE parametrisations in test_backends_schedulers.py. Adds coverage for the new load_config errors on removed cluster_types and settings, and makes test_all_cluster_types read SUPPORTED_CLUSTER_TYPES instead of a stale hardcoded list.
Deletes docs/kubernetes_testing.md and the three PRICING_* guides outright -- they document the Kubernetes backend and the cost/pricing API, all of which are gone from the code. Rewrites the dead cluster_type="kubernetes" examples in testing_guidelines.md and migration_to_real_tests.md against HuggingFace Jobs and SLURM, removes the PBS/SGE/Kubernetes sections and the deleted test-file references from REAL_CLUSTER_JOB_TESTING.md, and adds a scoping note to CREDENTIAL_SETUP.md saying which credentials still reach an execution backend.
…lambda_cloud) Credential plumbing for backends that were never verified against real hardware is deleted along with the backends themselves. Retained: ssh (used by the ssh and slurm backends), huggingface (HF Jobs) and local. - credential_manager.py: drop the provider entries from all three credential sources, from every provider list, from the generated .env template, and delete ensure_kubernetes_provider_credentials plus its module-level wrapper (no remaining callers anywhere in the tree). - secure_credentials.py: drop ValidationCredentials.get_aws_credentials, get_gcp_credentials, get_lambda_cloud_credentials and the unconditional get_docker_credentials stub. - setup_validation_credentials.py: drop the AWS/GCP/Lambda/Docker entries and the pointers to two validation scripts that do not exist. - test_credential_manager.py: retarget the AWS/Azure assertions at ssh and huggingface, and add parametrized tests asserting the removed providers stay unresolvable even with their env vars set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
… cluster_type comment The 0.2.0 'Fixed' list described defects in backends the same release removes; the Kubernetes and cloud-placeholder entries are dropped, the PBS one is generalised, and a short preface says why the remainder are kept. Removes the Lambda Cloud credential setup from CREDENTIAL_SETUP.md and fixes the 'or "pbs", "sge"' comment in the SSH key automation notebook.
hf_sdk was a HuggingFace *Spaces* concept -- the gradio/streamlit/static
SDK a Space runs under. hf_jobs.py never reads it; the names it does read
are hf_token, hf_namespace, hf_username, hf_image, hf_flavor, hf_hardware,
hf_allow_gpu_flavors, hf_payload_repo and hf_job_timeout. With Spaces gone
the field configured nothing, so it is removed from ClusterConfig and from
the legacy widget, and added to the removed-settings table so an existing
config file gets an explanation rather than a difflib guess.
scripts/setup_validation_credentials.py is deleted. Every run of it ended
at the same place:
❌ 1Password CLI not available!
📥 To install 1Password CLI: macOS: brew install --cask 1password-cli
main() returns 1 there and never reaches the setup guide or the credential
test. SecureCredentialManager.is_op_available() has returned False
unconditionally since 1Password support was removed in #97, so the script
has been unreachable past its first check and was telling readers to
install a CLI clustrix no longer uses. `clustrix credentials setup` and
`clustrix credentials test` already do the job for real. scripts/README.md
now points at those.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
Follows the field's removal: it configured the HuggingFace Spaces SDK and nothing reads it now. test_widget_fixes.py: 8 passed, 1 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
…ials are gone tests/infrastructure/docker-compose.yml no longer starts a Kind cluster and clustrix.credential_manager now reads only SSH_* and HF_*. Updates the README's test-infrastructure list, the migration guide's infrastructure validator, and the CREDENTIAL_SETUP scope note to match.
It describes a Kind/Kubernetes test service that no longer exists.
RealWorldCredentialManager loses get_aws/azure/gcp/lambda_cloud/kubernetes credentials and the matching env-var exports; TestCredentials, the aws/azure/gcp conftest fixtures and the aws_required/azure_required/ gcp_required markers go with them, since nothing retained consumes them. test_credential_access.py::test_validation_credentials was calling ValidationCredentials.get_lambda_cloud_credentials/get_aws_credentials/ get_gcp_credentials, none of which exist on that class -- the test raised AttributeError. It now probes the two credential types the class actually exposes (HuggingFace, SSH).
Removes the PBS, SGE, Kubernetes and cloud-provider cases from test_executor.py, test_enhanced_features.py, test_config_real.py, test_integration.py and test_config_file_permissions.py. Where the property under test was general (save/load round trip, a failed cancellation keeping the job tracked, an SSH script carrying no scheduler directives) it is restated over slurm/ssh/huggingface rather than deleted.
…backends run_real_world_tests: drop the cloud_providers and kubernetes categories, the kubectl/AWS/GCP/Azure availability probes and their CLI choices. test_visual_verification: the widget profile assertion still required an "AWS Batch" profile after the profile itself became HuggingFace Jobs, so it could never pass; assertion updated to match. The synthetic matplotlib demo labels no longer advertise PBS/SGE/K8s/cloud providers as supported. REFACTORING_PROGRESS.md: rows describing deleted modules corrected and a dated backend-removal section added.
…SSH validator tests/integration/conftest.py named test_eks_permissions.py and test_aws_eks_debug.py as the motivating examples; neither file exists any more, so the rationale is restated without them. The guard itself is unchanged -- it still stops collection directory-wide. validate_ssh_cluster_access no longer probes remote hosts for PBS, SGE and LSF binaries, since clustrix cannot submit to any of them.
tests/reference_workflows/kubernetes_workflows.py went with the backend, but test_reference_workflows.py still imported two workflows from it, so `pytest tests/` aborted during collection and no test in the suite ran. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
This was referenced Aug 19, 2026
The removal section said a removed backend raises "Unsupported cluster type" at submit time. validate_cluster_type changed both the message and the timing -- it is now refused at construction, at configure(), at load_config and in the executor before it connects. Quotes the real messages, including the typo case that must keep its did-you-mean hint. Also records the API surface that went with the backends (configure's auto_install_deps, ten @cluster parameters, the config fields, hf_sdk), and adds the two test-suite defects found in this pass: #147 and the host-key tests writing into the developer's real known_hosts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
`pip install clustrix[aws]` pulled boto3 and the Kubernetes client for code
that is not in the package. The `kubernetes`, `aws`, `azure`, `gcp` and
`cloud` extras are gone, and the cloud SDKs are out of `test` and `all` too.
Nothing in `clustrix/` imports any of them:
$ grep -rn "import boto3\|from kubernetes\|from azure\|from google.cloud" clustrix/
(no matches)
`scripts/aws/` still uses boto3, but deliberately imports it lazily with its
own message -- "boto3 is not a clustrix dependency; these AWS utilities are
optional" -- so it never needed an extra either.
CI's install line drops the extra with them:
- pip install -e ".[dev,test,kubernetes,widget]"
+ pip install -e ".[dev,test,widget]"
Verified by resolving the new line in a clean 3.11 venv: `pip install
--dry-run -e ".[dev,test,widget]"` succeeds and the resolved set contains no
boto3, kubernetes, azure-* or google-* package.
Also drops "kubernetes" from the package keywords, which advertised it on
PyPI, in favour of the backends that exist.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
3 tasks
sphinx_autodoc_typehints evaluates annotations across a documented class's whole MRO. ClusterfyMagics subclasses IPython's Magics, which annotates `shell: InteractiveShell` behind a TYPE_CHECKING guard, so the name does not exist in IPython.core.magic at runtime and get_type_hints() cannot resolve it. That emitted a forward_reference warning, fatal under sphinx-build -W and so blocking the docs gate entirely. Bind the name instead of adding the category to suppress_warnings, which would also hide the same class of warning in clustrix's own code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
…ef:` text nbsphinx renders a notebook markdown cell as Markdown, not reStructuredText, so an rst role in one is never resolved. The built HTML showed, verbatim, `:doc:<code>../ssh_setup</code>` and `:ref:<code>execution-model</code>` -- eight dead cross-references across five notebooks, and meaningless text for anyone reading the same notebook in Colab or on GitHub, which is where the Open in Colab badges send them. Replaced with absolute readthedocs links, which resolve in all three contexts. Each target URL was fetched and returned 200. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
real-world-tests.yml exported LAMBDA_CLOUD_API_KEY, GCP_PROJECT_ID, GCP_JSON, AWS_ACCESS_KEY_ID and AWS_ACCESS_KEY into four steps. Every backend that read them is deleted, so they were doing nothing except widening the blast radius of a compromised step: a job that cannot use a credential should not be given one. What remains maps exactly to the backends that survive -- CLUSTRIX_USERNAME and CLUSTRIX_PASSWORD for ssh/slurm, HF_USERNAME and HF_TOKEN for HuggingFace Jobs -- and the check-secrets gate already keys on precisely those. Worth recording alongside this: every step in this workflow invokes `python scripts/run_real_world_tests.py --<category>` as a bare command, and Actions fails a step on a non-zero exit. Because that script exited 0 no matter what (#147, fixed in 9b90eaa), **this workflow could not fail either** -- the same defect as the pre-push hook, in the one workflow whose entire purpose is to validate against real clusters. It can fail now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
Verification found four classes of defect, all fixed in the notebooks: Missing Colab badges. cluster_config_example.ipynb and filesystem_tutorial.ipynb had none, while the other five did. Added, pointing at the same path the file occupies in the repo. Widget-on-import is no longer true. ssh_tutorial.ipynb and ssh_key_automation_tutorial.ipynb both told the reader to look for a widget that 'appeared automatically' when clustrix was imported. Importing only registers the %%remote magic. ssh_key_automation_tutorial went further and walked through an 'SSH Key Setup' section with its own host/user/password fields and a 'Setup SSH Keys' button -- that is the legacy EnhancedClusterConfigWidget, not the ModernClustrixWidget %%remote shows, which has an 'Auto setup SSH keys' button in its Connection section. Wrong Colab secret names. The tutorial told readers to store a Colab secret as CLUSTER_PASSWORD_CLUSTER_UNIVERSITY_EDU. get_cluster_password()'s Colab branch tries CLUSTER_PASSWORD_<raw hostname> first -- dots intact, not upper-cased -- so that name is never read from Colab secrets. Replaced with the actual list, in the actual order, for both the Colab and the environment-variable paths. Removed features in Next Steps. ssh_key_automation_tutorial still pointed at cloud provider integrations and cost monitoring, both deleted in v0.2.0. Also added what was missing rather than wrong: a job-directory autopsy for slurm_tutorial and ssh_tutorial (exact path, every file in it, and the three distinct failure modes), a 'When You Would Not Want This' section for basic_usage, and a step-by-step account of what setup_ssh_keys() really does. Every claim was checked against clustrix/ before it was written down. All seven notebooks re-executed; the only remaining failures are the placeholder hostnames in slurm_tutorial and ssh_tutorial, which need a real cluster. sphinx -b html -W --keep-going passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
_wait_for_scheduler_result polled under a bare `while True` with no
deadline and no timeout field anywhere in ClusterConfig. A job that never
reached a terminal state -- held by the scheduler, sitting behind a queue
that never cleared, a node stuck draining -- hung the caller forever, with
no diagnostic and no way out but Ctrl-C. This is on the primary path of
both verified scheduler backends.
New `job_wait_timeout`, default 86400 (24 hours). Deliberately generous:
a real HPC queue wait legitimately runs into hours, so a short default
would break correct usage. Set it to None for the old unbounded wait.
On expiry the job is deliberately NOT cancelled -- it may still be queued,
and killing someone's allocation because the client got bored is not this
function's call. The error names the job, the elapsed limit and the
setting that controls it, the last status seen, and the remote directory
the result can still be collected from:
TimeoutError: Job job_1 did not finish within 2s
(config.job_wait_timeout). Its last known status was 'running'. The job
has NOT been cancelled; its files are at
/scratch/someone/.clustrix/jobs/job_1 on the cluster. Raise
job_wait_timeout, or set it to None to wait indefinitely.
tests/unit/test_job_wait_timeout.py covers it with no mocks: a real
ClusterExecutor running the production loop, against a real subclass of
the real SchedulerManager whose job never leaves the queue. Four tests --
it gives up near the deadline rather than a multiple of it, the default is
finite, None really does remove the deadline (observed still polling well
past a deadline that would have fired), and an unknown job id is rejected
before any polling happens. 4 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
scripts/check_docs_examples.py executes every code block the documentation
publishes -- 143 of them, 110 for real -- and it passes. Nothing ran it:
$ grep -rn check_docs_examples .github/
(no matches)
So the docs were correct only for as long as someone remembered to check by
hand, and stale examples have been this project's largest recurring defect
class. The step runs once on ubuntu/3.11; the examples are not
version-specific, so running it seven times would only cost minutes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
This was referenced Aug 19, 2026
Closed
`grep -rn fail_under` across pyproject/setup.cfg/pytest.ini/CI returned
zero hits. The old `fail_under = 90` was removed because it was set
against a coverage number nobody could reproduce -- correct -- but nothing
replaced it, so the project measured 68% and gated on nothing.
Measured on this tree, with the command CI runs:
TOTAL 7180 stmts 2311 missing 68%
1244 passed, 18 skipped, 17 deselected
`fail_under = 66` -- two points of headroom so a version-to-version
difference across the 3.10/3.11/3.12 matrix cannot turn a green run red,
while a real regression still does. A floor and a ratchet, not a target;
90 remains the goal in #98 and this is not a claim to have reached it.
Verified it fires rather than being decorative, by running a single test
file under the same coverage command:
FAIL Required test coverage of 66.0% not reached. Total coverage: 14.40%
EXIT CODE: 1
CI already passes `--cov=clustrix` on every matrix job (tests.yml:81), so
this needed no workflow change to take effect.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
The step I added in b67a56d failed on ubuntu/3.11 with: File "scripts/check_docs_examples.py", line 695, in main rel = target.path.relative_to(REPO_ROOT) ValueError: '/opt/hostedtoolcache/.../site-packages/clustrix/config.py' is not in the subpath of '/home/runner/work/clustrix/clustrix' It sat after "Test installation", which does a non-editable `pip install .`. After that, `import clustrix` resolves to site-packages, so `inspect.getsourcefile` returned a path outside the checkout and the relative_to blew up. Two changes, because the ordering bug hid a real one: 1. The step now runs BEFORE "Test installation". That is where it belongs -- it checks the docs in this checkout against the code in this checkout. 2. The checker now refuses a foreign install by name instead of crashing inside pathlib. Getting the path arithmetic to survive would have been worse than the crash: the examples would have been silently checked against a *different copy* of the code, and passed while proving nothing. Reproduced the failure locally in a venv with a non-editable install, run from outside the repo, and confirmed the new message: documented module 'clustrix.config' imports from .../site-packages/clustrix/config.py, which is outside this checkout (/Users/jmanning/clustrix). The examples here would be checked against a different copy of the code. Install the package editable (pip install -e .) or run this before a non-editable install. Normal path unaffected: 143 blocks checked, 143 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
Records which issues were closed with what evidence, the re-measured figures that contradict the backlog, and the two items that need the user. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
…uire `master` had no branch protection at all -- `gh api repos/ContextLab/clustrix/branches/master/protection` returned "Branch not protected" -- so every green run this project has fixed was advisory. A red run could always be merged past. Protection needs required check names. The Tests workflow had none that is stable: pinning it to the seven `test (os, version)` jobs individually means protection silently stops covering any combination added later, and breaks whenever the matrix changes. Fast CI already solved this with its `CI Status` aggregator; this is the same pattern for the workflow that runs the actual test suite. `if: always()` matters: without it the job would be *skipped* when a dependency fails, and a skipped required check does not block a merge -- the gate would be worse than none. The condition names every job in `needs` explicitly, because the version of this in fast_ci.yml had security-scan in `needs` but not in its loop, and so reported "All CI checks passed" while the security scan burned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Removal of every execution backend that has never been run against real
hardware, and of everything that existed only to serve them. 43 commits,
127 files deleted, 63,437 lines removed.
The supported set is now exactly the four backends that have been
demonstrated end to end:
Removed:
pbs,sge,kubernetes,aws,gcp,azure,lambda_cloud,huggingface_spaces. Each has a tracking issue with a file/line inventory,the introducing commit, and what it would take to bring it back:
#140 PBS, #141 SGE, #142 Kubernetes, #143 AWS, #144 GCP, #145 Azure,
#146 Lambda Cloud.
Nothing here is a claim that the removed code was broken. It is a claim that
nobody has ever seen it work, and that offering it in the cluster-type
dropdown said otherwise.
What a user who was relying on one of these now sees
The old behaviour for a stale
clustrix.ymlwasdifflibguessing:k8s_namespacecame back as "did you mean ...?" pointed at an unrelatedfield, and
cluster_type: pbswas accepted silently and failed much later.The did-you-mean path is deliberately still reachable. A removed setting must
not look like a spelling mistake, and a spelling mistake must not look like a
removed setting.
validate_cluster_type()is now the single check, called fromClusterConfig.__post_init__, fromconfigure(), fromload_configand fromthe executor. Previously only
load_configchecked, so every other routecarried a dead backend all the way to
ClusterExecutor.submit_job— whichtested the type after
self.connect(). Asking for a backend that no longerexists cost an SSH round trip to a host that was never going to be used.
configure()also validates before it applies anything. It used tosetattrits way through kwargs and raise partway, so a call that failed had still
changed the live configuration.
Breaking changes
cost_tracking_decorator,get_cost_monitor,start_cost_monitoring,generate_cost_report,get_pricing_info(plusResourceUsage,CostEstimate,CostReport). Its dispatcher handled exactlylambda/aws/azure/gcp; with those gone it was a public API that could only
return
None.configure(auto_install_deps=...)is gone. It installed cloud providerdependencies.
@clusterloses ten parameters:provider,instance_type,region,platform,auto_provision,cluster_name,node_count,node_type,kubernetes_version,from_scratch.k8s_*,aws_*,azure_*,gcp_*,lambda_*,cloud_*andcost_monitoringfield is offClusterConfig.Two things worth a second look
hf_hardware,hf_usernameandhf_sdksat under a comment reading"Hugging Face Spaces settings". Two of them are read by
hf_jobs.py— theone verified API backend — as fallbacks for
hf_flavorandhf_namespace.Deleting on the comment's word would have broken it. They are kept and the
comment is corrected.
hf_sdkgenuinely was Spaces-only (thegradio/streamlit/static SDK) and nothing reads it, so it went.
The cluster-type list had drifted into three hardcoded copies.
enhanced_notebook_widget.pystill offered pbs, sge, kubernetes, aws, azureand gcp. It now reads
SUPPORTED_CLUSTER_TYPES, which is why that constantexists.
Defects found and fixed along the way
#147 — the pre-push hook could not block a push.
run_real_world_tests.pydropped every
runner.run_*_tests()return value and never calledsys.exit,so it exited 0 no matter what. All four categories printed "failed" and the
hook then announced
✅ All real-world tests passed!and allowed the push.Same class as the
flake8 --exit-zeroandmypy continue-on-errorsteps fixedin #138. Verified by appending a deliberately failing test:
Before the change that same case exited 0 with an empty message body — the
second half of #147: only
stdoutwas printed, and a pytest collection errorgoes to
stderr.The host-key tests were writing into the developer's real
~/.ssh/known_hosts— 83 stale[127.0.0.1]:<ephemeral port>entries hadaccumulated, and port reuse against a fresh server key then raised
BadHostKeyExceptionand failed the reject test. The fixture now redirects~. The 83 entries in the user's own file were left alone.scripts/setup_validation_credentials.pywas unreachable. Every run endedat
❌ 1Password CLI not available!and told the reader tobrew install --cask 1password-cli— butis_op_available()has returnedFalseunconditionally since 1Password support was removed in #97. Deleted;clustrix credentials setupalready does the job for real.tests/test_config.ymlwas a committed test artifact. A saved profilesfile that
load_confighas never been able to read, referenced by nothing,still carrying
aws_*/k8s_*/cost_monitoringkeys.Formatting was checked with the wrong black. Seven files were formatted by
black 25.11.0 while the project pins
black==26.3.1in bothpyproject.tomland
setup.py. A localblack --checkpassed where CI would have failed.Also filed, not fixed here
#148 — the real-world SSH tests bypass host-key verification.
set_missing_host_key_policy(paramiko.AutoAddPolicy())appears at 37 sitesacross 29 files under
tests/, whichclustrix/ssh_security.pyandCLAUDE.mdboth forbid.clustrix/itself complies. The consequence is thatthe host-key verification path every real user gets is never exercised by a
real SSH connection.
It is not fixed here because the one-line change is deliberately not
behaviour-preserving:
configure_host_key_policydefaults to"reject", soeach of those files needs to be run against its real cluster to confirm the
host is trusted and the test still passes. A blind sweep would trade a silent
security gap for a silently broken test suite.
Added after this PR was opened
Reviewing the open issue backlog against the code as it now stands surfaced
several things worth fixing here rather than tracking.
A real correctness bug: the job wait loop had no deadline (#123).
_wait_for_scheduler_resultpolled under a barewhile True, with no timeoutfield anywhere in
ClusterConfig. A job that never reached a terminal state —held by the scheduler, behind a queue that never cleared, a node stuck draining
— hung the caller forever, with no diagnostic and no way out but Ctrl-C. This
is on the primary path of both verified scheduler backends.
New
job_wait_timeout, default 86400. Deliberately generous: a real HPC queuewait legitimately runs into hours, so a short default would break correct
usage.
Nonerestores the old unbounded wait. On expiry the job is notcancelled — it may still be queued, and killing someone's allocation because
the client got bored is not this function's call:
Four tests, no mocks: a real
ClusterExecutorrunning the production loopagainst a real subclass of the real
SchedulerManagerwhose job never leavesthe queue.
Documentation examples now run in CI (#124).
scripts/check_docs_examples.pyexecutes 143 blocks, 110 of them for real, and passed — but
grep -rn check_docs_examples .github/returned nothing, so the docs were correct onlyfor as long as someone remembered to check by hand.
Adding the step exposed a second defect. It first failed with
ValueError: '.../site-packages/clustrix/config.py' is not in the subpath of '/home/runner/work/clustrix/clustrix', because it ran afterTest installation's non-editablepip install .. Making the path arithmetictolerant would have been worse than the crash — the examples would then have
been checked against a different copy of the code and passed while proving
nothing. The step moved before the install, and the checker now refuses a
foreign install by name.
Coverage has a floor that can fail (#115).
grep -rn fail_underreturnedzero hits anywhere: the old
fail_under = 90had been removed asunreproducible, and nothing replaced it, so the project measured 68% and gated
on nothing. Set to 66 — two points of headroom so a version difference across
the matrix cannot turn a green run red, while a regression still does. Verified
it fires rather than decorating: one test file under the same command exits 1
with
Required test coverage of 66.0% not reached. Total coverage: 14.40%.Packaging extras installed SDKs for deleted backends.
pip install clustrix[aws]pulled boto3 and the Kubernetes client for code that is not inthe package. The
kubernetes,aws,azure,gcpandcloudextras aregone, along with the cloud SDKs in
testandall, and CI's install linedrops the extra with them. Nothing in
clustrix/imports any of them;scripts/aws/imports boto3 lazily with its own "not a clustrix dependency"message. Verified by resolving the new line in a clean 3.11 venv.
real-world-tests.ymlhanded cloud credentials to jobs that cannot usethem —
LAMBDA_CLOUD_API_KEY,GCP_PROJECT_ID,GCP_JSONand two AWS keysacross four steps. Removed; what remains maps exactly to the surviving
backends.
#147 had a second and worse consumer. Every step in
real-world-tests.ymlinvokes the runner as a bare command, so Actions would fail the step on a
non-zero exit — but the script always exited 0. That workflow is the only one
that runs the real-world suite, so the single thing exercising clustrix against
real clusters had been reporting green unconditionally.
Notebook fixes. All seven surviving tutorials execute locally (the 10
failing cells across
slurm_tutorialandssh_tutorialare DNS on placeholderhostnames — they need a cluster). Eight
:doc:/:ref:roles sat in markdowncells, so the built HTML literally displayed
:ref:`execution-model`` toreaders; two notebooks had no Colab badge; both SSH notebooks claimed the
widget appears on
import clustrix, which is false; `ssh_key_automation`documented the legacy widget rather than the one `%%remote` shows; and it
named a Colab secret that is never read. Colab rendering was verified for the
badged notebooks without signing in — so "renders in Colab", not "runs in
Colab".
Verification
Every check below was run on the final tree, in one cycle, after the last
change:
Nothing under
tests/real_world/ortests/integration/was executed —tests/integration/provisions real billable AWS resources. Both were checkedwith
--collect-only: 158 and 37 collected, no import errors.All 15 CI checks pass on the branch tip, including both Windows jobs, all
three Ubuntu versions, both macOS versions, and the new documentation-examples
step (confirmed as
success, not skipped).A note on the history
07db2e6is a deliberate WIP checkpoint whose tree does not import — itwas committed so work would survive a machine suspend, and it is labelled
DO NOT MERGE. The branch tip is clean and passes everything above. Squash onmerge, or the broken commit stays bisectable.
🤖 Generated with Claude Code
https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU