Skip to content

Remove every backend that has never been run against real hardware - #149

Merged
jeremymanning merged 56 commits into
masterfrom
remove/unverified-backends
Aug 19, 2026
Merged

Remove every backend that has never been run against real hardware#149
jeremymanning merged 56 commits into
masterfrom
remove/unverified-backends

Conversation

@jeremymanning

@jeremymanning jeremymanning commented Aug 19, 2026

Copy link
Copy Markdown
Member

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:

SUPPORTED_CLUSTER_TYPES = ("local", "ssh", "slurm", "huggingface")

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.yml was difflib guessing:
k8s_namespace came back as "did you mean ...?" pointed at an unrelated
field, and cluster_type: pbs was accepted silently and failed much later.

load_config, file containing `cluster_type: pbs`
  ValueError: <path>: cluster_type='pbs' is no longer implemented. It was removed
  in v0.2.0 because it had never been verified against real hardware. Its return
  is tracked in issue #140. Supported types are: local, ssh, slurm, huggingface.

load_config, file containing `k8s_namespace: compute`
  ValueError: <path> contains unknown setting(s): k8s_namespace configured
  Kubernetes, which has been removed (see issue #142)

load_config, file containing a genuine typo `cluster_hostt`
  ValueError: <path> contains unknown setting(s): cluster_hostt
  (did you mean cluster_host?)

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 from
ClusterConfig.__post_init__, from configure(), from load_config and from
the executor. Previously only load_config checked, so every other route
carried a dead backend all the way to ClusterExecutor.submit_job — which
tested the type after self.connect(). Asking for a backend that no longer
exists cost an SSH round trip to a host that was never going to be used.

configure() also validates before it applies anything. It used to setattr
its way through kwargs and raise partway, so a call that failed had still
changed the live configuration.

Breaking changes

  • The cost-monitoring API is gone, with all five exported functions:
    cost_tracking_decorator, get_cost_monitor, start_cost_monitoring,
    generate_cost_report, get_pricing_info (plus ResourceUsage,
    CostEstimate, CostReport). Its dispatcher handled exactly
    lambda/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 provider
    dependencies.
  • @cluster loses ten parameters: provider, instance_type, region,
    platform, auto_provision, cluster_name, node_count, node_type,
    kubernetes_version, from_scratch.
  • Every k8s_*, aws_*, azure_*, gcp_*, lambda_*, cloud_* and
    cost_monitoring field is off ClusterConfig.

Two things worth a second look

hf_hardware, hf_username and hf_sdk sat under a comment reading
"Hugging Face Spaces settings".
Two of them are read by hf_jobs.py — the
one verified API backend — as fallbacks for hf_flavor and hf_namespace.
Deleting on the comment's word would have broken it. They are kept and the
comment is corrected. hf_sdk genuinely was Spaces-only (the
gradio/streamlit/static SDK) and nothing reads it, so it went.

The cluster-type list had drifted into three hardcoded copies.
enhanced_notebook_widget.py still offered pbs, sge, kubernetes, aws, azure
and gcp. It now reads SUPPORTED_CLUSTER_TYPES, which is why that constant
exists.

Defects found and fixed along the way

#147 — the pre-push hook could not block a push. run_real_world_tests.py
dropped every runner.run_*_tests() return value and never called sys.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-zero and mypy continue-on-error steps fixed
in #138. Verified by appending a deliberately failing test:

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

Before the change that same case exited 0 with an empty message body — the
second half of #147: only stdout was printed, and a pytest collection error
goes 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 had
accumulated, and port reuse against a fresh server key then raised
BadHostKeyException and failed the reject test. The fixture now redirects
~. The 83 entries in the user's own file were left alone.

scripts/setup_validation_credentials.py was unreachable. Every run ended
at ❌ 1Password CLI not available! and told the reader to
brew install --cask 1password-cli — but is_op_available() has returned
False unconditionally since 1Password support was removed in #97. Deleted;
clustrix credentials setup already does the job for real.

tests/test_config.yml was a committed test artifact. A saved profiles
file that load_config has never been able to read, referenced by nothing,
still carrying aws_* / k8s_* / cost_monitoring keys.

Formatting was checked with the wrong black. Seven files were formatted by
black 25.11.0 while the project pins black==26.3.1 in both pyproject.toml
and setup.py. A local black --check passed 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 sites
across 29 files under tests/, which clustrix/ssh_security.py and
CLAUDE.md both forbid. clustrix/ itself complies. The consequence is that
the 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_policy defaults to "reject", so
each 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_result polled under a bare while True, with no timeout
field 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 queue
wait legitimately runs into hours, so a short default would break correct
usage. None restores the old unbounded wait. On expiry the job is not
cancelled — it may still be queued, and killing someone's allocation because
the client got bored is not this function's call:

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.

Four tests, no mocks: a real ClusterExecutor running the production loop
against a real subclass of the real SchedulerManager whose job never leaves
the queue.

Documentation examples now run in CI (#124). scripts/check_docs_examples.py
executes 143 blocks, 110 of them for real, and passed — but grep -rn check_docs_examples .github/ returned nothing, so the docs were correct only
for 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 after Test installation's non-editable pip install .. Making the path arithmetic
tolerant 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_under returned
zero hits anywhere: the old fail_under = 90 had been removed as
unreproducible, 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 in
the package. The kubernetes, aws, azure, gcp and cloud extras are
gone, along with the cloud SDKs in test and all, and CI's install line
drops 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.yml handed cloud credentials to jobs that cannot use
them
LAMBDA_CLOUD_API_KEY, GCP_PROJECT_ID, GCP_JSON and two AWS keys
across four steps. Removed; what remains maps exactly to the surviving
backends.

#147 had a second and worse consumer. Every step in real-world-tests.yml
invokes 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_tutorial and ssh_tutorial are DNS on placeholder
hostnames — they need a cluster). Eight :doc:/:ref: roles sat in markdown
cells, so the built HTML literally displayed :ref:`execution-model `` to
readers; 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:

pytest tests/ -m "not real_world" --ignore=tests/real_world --ignore=tests/integration
    1244 passed, 18 skipped, 0 failed
    Required test coverage of 66.0% reached. Total coverage: 67.81%

black --check clustrix/ tests/ scripts/     224 files unchanged   (black 26.3.1, the pinned version)
flake8 clustrix/ tests/ scripts/            clean
mypy clustrix/                              no issues in 34 source files
python scripts/check_docs_examples.py       143/143 passed (110 executed for real)
python -m sphinx -b html docs/source ... -W  build succeeded, zero warnings

Nothing under tests/real_world/ or tests/integration/ was executed —
tests/integration/ provisions real billable AWS resources. Both were checked
with --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

07db2e6 is a deliberate WIP checkpoint whose tree does not import — it
was 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 on
merge, or the broken commit stays bisectable.

🤖 Generated with Claude Code

https://claude.ai/code/session_012gTBDPK16HUZ3kHQ2QyjuU

jeremymanning and others added 30 commits August 19, 2026 09:54
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
jeremymanning and others added 3 commits August 19, 2026 11:26
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
jeremymanning and others added 7 commits August 19, 2026 11:48
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
jeremymanning and others added 4 commits August 19, 2026 12:03
`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
@jeremymanning
jeremymanning merged commit f78d153 into master Aug 19, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant