Skip to content

Restore Python 3.7 buildability, bump to 0.3.3 - #25

Open
Willmac16 wants to merge 28 commits into
mainfrom
claude/translatemodel-issue-24-788emh
Open

Restore Python 3.7 buildability, bump to 0.3.3#25
Willmac16 wants to merge 28 commits into
mainfrom
claude/translatemodel-issue-24-788emh

Conversation

@Willmac16

Copy link
Copy Markdown
Owner

0.3.2 cannot be installed on OctoPi 0.18 and older images (Python 3.7):
pip fails at "Installing build dependencies" with "Could not find a
version that satisfies the requirement setuptools>=77", because the
newest setuptools release that still supports Python 3.7 is 68.0.0.

Two things in 0.3.2 forced that floor, and both hard-error against
setuptools 68:

  • license as a PEP 639 SPDX string plus a top-level license-files
    needs setuptools >=77; older versions reject it with
    "project.license must be valid exactly by one definition".
  • [[tool.setuptools.ext-modules]] only landed in setuptools 74.1;
    older versions reject it with "tool.setuptools must not contain
    {'ext-modules'} properties".

So drop the build floor to setuptools>=61 (first release with full
[project] support, and satisfied by 68.0.0 on Python 3.7), move the
license back to the pre-PEP-639 table form with license-files under
[tool.setuptools], and declare the C extension in a minimal setup.py
again. The shim only calls setup(ext_modules=...) -- all metadata stays
static in pyproject.toml, and unlike the setup.py removed in c4349f3 it
does not need octoprint_setuptools in the build environment.

Verified end to end against setuptools 68.0.0 (the Python 3.7 ceiling)
and setuptools 83.0.0: the extension compiles, the wheel contains
octoprint_translatemodel/_translate*.so with no C++ source, LICENSE and
the octoprint.plugin entry point are recorded, and the extension imports
after install. Current setuptools only warns that the license spelling is
deprecated (removal announced for 2027-Feb-18), noted inline for when the
Python floor eventually rises.

Fixes #24

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva

claude added 28 commits July 30, 2026 11:18
0.3.2 cannot be installed on OctoPi 0.18 and older images (Python 3.7):
pip fails at "Installing build dependencies" with "Could not find a
version that satisfies the requirement setuptools>=77", because the
newest setuptools release that still supports Python 3.7 is 68.0.0.

Two things in 0.3.2 forced that floor, and both hard-error against
setuptools 68:

  - `license` as a PEP 639 SPDX string plus a top-level `license-files`
    needs setuptools >=77; older versions reject it with
    "`project.license` must be valid exactly by one definition".
  - `[[tool.setuptools.ext-modules]]` only landed in setuptools 74.1;
    older versions reject it with "`tool.setuptools` must not contain
    {'ext-modules'} properties".

So drop the build floor to setuptools>=61 (first release with full
[project] support, and satisfied by 68.0.0 on Python 3.7), move the
license back to the pre-PEP-639 table form with license-files under
[tool.setuptools], and declare the C extension in a minimal setup.py
again. The shim only calls setup(ext_modules=...) -- all metadata stays
static in pyproject.toml, and unlike the setup.py removed in c4349f3 it
does not need octoprint_setuptools in the build environment.

Verified end to end against setuptools 68.0.0 (the Python 3.7 ceiling)
and setuptools 83.0.0: the extension compiles, the wheel contains
octoprint_translatemodel/_translate*.so with no C++ source, LICENSE and
the octoprint.plugin entry point are recorded, and the extension imports
after install. Current setuptools only warns that the license spelling is
deprecated (removal announced for 2027-Feb-18), noted inline for when the
Python floor eventually rises.

Fixes #24

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Nothing has ever built this repo outside a maintainer's machine, which is
how 0.3.2 shipped a build-system requirement that no supported Python
could satisfy. This adds a workflow that performs an isolated PEP 517
build of the checked-out tree -- the same thing OctoPrint's software
update plugin does when it pip-installs archive/<tag>.zip -- across the
full requires-python range, then installs the wheel and checks it.

The matrix covers 3.8 through 3.13 via setup-python. 3.7 gets its own job
in the python:3.7-bullseye image, because setup-python's manifest has no
3.7 build for ubuntu-24.04 (it stops at 22.04); bullseye rather than
buster so actions/checkout's Node 20 has glibc >= 2.28.

Builds install with --no-deps, so each job takes seconds and never
resolves OctoPrint's dependency tree. That loses nothing: issue #24
failed while installing *build* dependencies, long before any runtime
dependency was considered.

check_build.py asserts the things this repo has actually broken before:
the compiled extension present and still named _translate inside the
package, no C++ source in the wheel, templates and static assets
packaged, LICENSE recorded, and the octoprint.plugin entry point
pointing at octoprint_translatemodel. It then loads the installed
extension by file path and calls it -- by file rather than by import,
since importing the parent package would need OctoPrint.

Verified by running the workflow's step sequence locally against
setuptools 68.0.0 (the 3.7 ceiling) and 83.0.0: both pass. Also verified
the checks bite -- a wheel with the extension renamed back to `translate`
and entry_points.txt removed exits 1 naming both faults.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The first run showed the workflow firing twice for the same SHA on a
branch with an open PR: once for push, once for pull_request. Limit the
push trigger to main so branch commits are covered by the PR event alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The build jobs deliberately install with --no-deps, which means they never
prove OctoPrint can actually find the plugin. This job installs the wheel
with its dependencies and walks the same path OctoPrint's plugin manager
takes at startup: resolve the octoprint.plugin entry point, import the
module behind it, call __plugin_load__(), and inspect the result -- no
server boot required.

It covers what check_build.py structurally cannot: an entry point that
resolves to nothing importable, a compiled extension that fails to load
through the normal `from . import _translate` path rather than by file,
and a plugin class that stops registering as the mixins OctoPrint
dispatches on. It also pins the installed version to pyproject's, so a
half-finished release bump gets caught.

Pinned to 3.11, the version OctoPi's bookworm image ships.

Verified locally against OctoPrint 1.11.8 on 3.11: all 18 checks pass,
install included takes about 20s. Both failure modes were confirmed to
fail cleanly -- stripping entry_points.txt from the installed dist-info
and removing the .so each exit 1 with a named FAIL rather than a
traceback.

Note for anyone extending these scripts: `pip wheel .` leaves an
egg-info directory in the checkout, so metadata lookups run from the
repo root can find that instead of the installed distribution. Both
scripts drop the working directory from sys.path to avoid it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
OctoPrint 2.0 has been in rc since April 2026 and is the release that
raises OctoPrint's own Python floor to 3.9, so it is worth knowing now
whether the plugin still loads under it rather than finding out when
users upgrade.

The discovery job becomes a matrix over OctoPrint version. The 2.0 leg
installs the plugin normally -- which resolves stable OctoPrint -- and
then upgrades in place, because that is the order real users hit it: the
plugin is already installed when OctoPrint moves to the new major. Both
legs run on 3.11, which OctoPi's bookworm image ships and which both
1.11 and 2.0 support. Each leg now also prints the OctoPrint version it
resolved, so a failure log says what it was actually testing.

Verified locally: the plugin loads under 2.0.0rc4 with all 18 checks
passing and no API drift -- every mixin still registers and the
softwareupdate hook is intact.

This leg is a hard gate rather than continue-on-error, so a regression in
a future rc is visible instead of silently green. The trade-off is that a
broken rc can turn the branch red for reasons outside this repo; if that
becomes a nuisance, adding continue-on-error to the 2.0 leg is the
one-line fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The first CI runs warned that checkout@v4 and setup-python@v5 target Node
20 and were being force-run on Node 24. v7 is the current major for both
(checkout v7.0.1, setup-python v7.0.0); both are ESM on Node 24 and want
runner >= 2.327.1, which GitHub-hosted runners are well past.

The one breaking change in checkout v7 is that fork PRs are no longer
checked out for pull_request_target and workflow_run without opting in via
allow-unsafe-pr-checkout. This workflow triggers on push, pull_request and
workflow_dispatch, so it is unaffected -- and the new default is the safer
one regardless.

Node 24 in the 3.7 container was already proven by the previous run, where
the runner force-ran the older actions on it and the job passed: bullseye
ships glibc 2.31 against Node 24's 2.28 floor. Comment updated to say so,
since it previously cited Node 20.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Until now CI proved the plugin builds, installs and loads, but nothing
checked the thing it exists to do. src/translate.cpp had no coverage at
all, and a regression there does not crash -- it silently emits wrong
coordinates and ruins a print.

tests/ drives the extension directly, no OctoPrint required: the wheel is
already installed by the build jobs, and conftest loads the compiled
module by file path so importing the parent package (which needs
OctoPrint) never happens. 20 tests cover single and multi-shift output,
relative-mode moves being left alone, Z/E/F passing through untouched,
G0-G3 versus G4, rounding, CRLF preservation, output file naming, the
version header, layer markers, and preview mode returning gcode rather
than a path.

Two of those deserve calling out, because writing the tests is what
surfaced them: translation only begins at the first layer-start match and
ends at the stop match, so start and end gcode are copied through
unshifted. That is correct and load-bearing -- shifting the priming line
would send it off the bed -- and it now has explicit tests rather than
being implicit in a regex.

The tests run inside the existing build jobs rather than a new one, so
they execute on every interpreter from 3.7 to 3.13; the extension is
compiled per-Python, so per-version coverage is worth having. On 3.7 pip
resolves pytest 7.4.4, the last release supporting it.

Verified the suite has teeth rather than just passing: swapping shift[0]
for shift[1] in the X branch of translateLine and rebuilding fails four
tests.

Also adds a 3.14 probe and Dependabot for actions. The probe forces the
build past the requires-python cap to answer whether <3.14 can be lifted
-- 3.14 shipped in October 2025 and OctoPrint 2.0 allows <3.15, so the cap
will start excluding people. It is continue-on-error because a failure is
a version this package already declares unsupported, unlike the OctoPrint
2.0 leg which gates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Probing the extension with malformed input found three ways to kill the
interpreter outright, all in translate_translate's argument handling:

  - An invalid layer-start or stop pattern threw std::regex_error out of
    translate() and into CPython, where an escaping C++ exception calls
    std::terminate (SIGABRT). This is the reachable one: those patterns
    come straight from plugin settings, so a typo in a settings field
    took the whole OctoPrint process down mid-print.
  - A shift with fewer than two entries read past the end of the
    sequence (SIGSEGV), as did a non-numeric coordinate, because
    PyNumber_Float's NULL return was passed straight to
    PyFloat_AS_DOUBLE.
  - A long enough shift list overflowed the stack (SIGSEGV): shifts was
    a variable-length array sized directly by the caller. One million
    shifts is 16MB against an 8MB stack.

So: catch std::exception around translate() and raise ValueError with
the message instead, validate each shift's length and coordinate
conversion, and move the shift array to a std::vector. An empty shift
list is now rejected too -- translate() reads shifts[0] on the
single-shift path, so it was reading off the end of a zero-length array.

The malformed-shift paths were not reachable through the plugin, which
coerces with float(shift[0]), float(shift[1]) and would raise in Python
first; they were still a segfault in the extension's public API. The
regex path had no such guard.

Also drops a stray Py_INCREF on the borrowed shift list argument, which
leaked a reference on every call, and the reference leaks in the shift
parsing loop that the rewrite subsumes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The logging wrappers built a message with Py_BuildValue, which already
returns a new reference, then took another with Py_XINCREF and released
only one. They also dropped the return value of PyObject_CallMethod,
which is a new reference too.

Measured against the previous commit by watching None's refcount, since
logger.debug() returns None: 2000 translate calls leaked 10000
references, five per call. After the fix the same measurement is flat at
zero, which also rules out over-releasing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
M555 tells a Prusa which bed region to probe for G29, and the copies a
translate produces sit outside the original footprint, so the probed area
has to grow to cover them. The arithmetic that does it -- new origin from
the smallest shift, new size grown by the spread between smallest and
largest -- had no coverage at all, and getting it wrong misconfigures
probing rather than failing loudly.

Eight tests over single and multiple shifts, negative shifts, shift order
independence, three-way extents, lowercase arguments, the
TRANSLATE-MODEL_BED_AREA marker, and gcode with no M555 at all. All pass
against the current implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
With more than one shift a layer is buffered and replayed once per copy,
so G90/G91 state has to be reset to the layer's entry state before each
replay -- otherwise the second copy inherits the first's trailing mode
and its moves come out unshifted. translateLine carries a separate flag
through the replay loop for exactly this reason, and nothing held it to
that.

Four tests: each copy starting from the layer entry state, the trailing
mode still applying to the following layer, mode switching mid-layer, and
the single-shift streaming path tracking the same state. All pass
against the current implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
__init__.py has a TODO about handling translation of an already
translated file, and it is reachable: output lands beside the input, so
it appears in OctoPrint's file list and can be picked again.

These tests record the current baseline rather than endorse it. Shifts
compound (10 -> 15 -> 20), the output name accumulates suffixes, and the
header is written once per pass. The fourth test is the one that matters
if this is ever changed: ;TRANSLATE-MODEL_LAYER_START does not match the
layer-start pattern, so a second pass does not treat first-pass markers
as new layers -- if that ever became true, copies would multiply on every
pass instead of adding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The systematic version of the bad-input work: rather than guessing which
malformed inputs reach a bad pointer, build the extension instrumented
and let the sanitizers say so. It would have caught the out-of-bounds
shift reads directly instead of by segfault.

Two things this needed. libstdc++ has to be preloaded next to libasan --
with only libasan preloaded into an uninstrumented interpreter, asan's
__cxa_throw interceptor cannot resolve the real symbol and the first C++
exception dies on a CHECK failure, which the invalid-regex tests trigger
deliberately. And ubsan needs halt_on_error, or it prints diagnostics and
still exits zero, so undefined behaviour would pass as green.

Leak detection stays off: CPython is not instrumented, so its own
allocations would bury anything real. This job covers memory errors and
UB; the reference-counting side is covered by the refcount measurement in
the previous commits.

Verified locally: all 50 tests pass instrumented with no findings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Every other job builds from the checked-out tree, because that is what
OctoPrint's updater installs from a GitHub archive. Nothing exercised the
sdist path, so MANIFEST.in dropping src/translate.cpp would only surface
after a PyPI upload -- and the 0.3.2 rename suggests PyPI is on the
cards.

This packs an sdist, builds the wheel from that tarball rather than the
tree, then runs the same content checks and translate tests against it.
Verified locally: the sdist carries src/translate.cpp and setup.py, and
the wheel builds from it cleanly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Every user of this plugin is on a Raspberry Pi and every job so far
builds x86. That gap matters for more than whether it compiles: plain
`char` is unsigned by default on ARM and signed on x86, and
translate.cpp walks gcode a character at a time, so a signedness
assumption would produce wrong output only on the hardware nobody tests
on.

Runs the same build, wheel checks and translate tests inside an arm64
container via qemu. Slower than the native jobs, but it is the only place
architecture-specific behaviour can show up before a user's print does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
runnDict and finishDict were assigned without var/let/const, which makes
them implicit globals: they land on window and are shared by every
instance of the view model rather than being scoped to the branch that
builds them. Both are used only within the block that assigns them, so
const is a straight swap.

Found by running eslint over the file, which is added in the next commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
static/js is the plugin's browser-facing surface and the part that needed
XSS fixes in 0.3.1, and nothing has ever looked at it automatically.

eslint's recommended rules plus eslint-plugin-no-unsanitized, with
OctoPrint's injected globals declared so the job reports real problems
rather than a wall of no-undef. Callback parameters are exempt from
no-unused-vars, which is the usual convention and the only rule
relaxation here.

Worth being clear about the limit: no-unsanitized covers DOM sinks like
innerHTML and insertAdjacentHTML, not jQuery's .html(). The one .html()
call in this file passes a jQuery object rather than a string, so it is
not a sink, but a future string argument would not be flagged by this
job.

package.json is dev-only tooling -- it is not published and does not
reach the wheel, which is built from pyproject.toml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The software update plugin fetches archive/<tag>.zip and compares the tag
against the installed distribution's version, so tagging 0.3.4 while
pyproject.toml still says 0.3.3 ships an update that reports the wrong
version once installed. The bump has historically been its own commit,
which is the kind of step that gets skipped.

Runs on tag pushes and compares the two, tolerating a v prefix in case
tagging style ever changes. Verified both directions: a matching tag
exits 0, a mismatched one exits 1 naming both versions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Everything so far tested the extension or the packaging; the plugin
module itself had no coverage. These tests run against a bare plugin
instance with small fakes for the printer, file manager and logger.

Covered: the settings defaults, including a check that they are valid
patterns for the C++ side (std::regex, not Python's re -- an invalid
default would make every translate raise) and that they match the markers
Cura and PrusaSlicer actually emit; the software update configuration
pointing at this repository and reporting the running version; the
declared JS asset existing in the installed package; the API command
parameter lists; and the delete-after-print handler, which must fire only
for tracked files, only on print-end events, and only for local origin,
and must stop tracking the file so a later event cannot delete twice.

on_api_command is deliberately not covered: it opens with a
flask-principal permission check that needs a real request and app
context, which is a bigger harness than the rest of this is worth.

The tests skip themselves when OctoPrint is not importable, so the build
jobs -- which install with --no-deps -- skip them and the discovery job,
which has a real OctoPrint, runs them. Verified both ways: 65 pass with
OctoPrint present, 50 pass and 1 skip without.

conftest now drops the working directory from sys.path at import time
rather than inside the extension loader, because these tests import the
installed package at collection time and the repo's source directory
would otherwise shadow it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The lint job failed on npm error notarget: @eslint/js is versioned
independently of eslint, and I wrote ^10.8.0 for both by assuming they
track together. eslint is 10.8.0; @eslint/js is 10.0.1.

Verified this time from a clean npm install against package.json, which
is what CI does, rather than from packages installed ad hoc.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The 3.14 probe passes but warns: PyImport_ImportModuleNoBlock is
deprecated and removed in Python 3.15. It has been nothing but an alias
for PyImport_ImportModule since Python 3.3, so this is behaviour-neutral
today and the difference between building and not building on 3.15.

Verified with deprecation warnings turned into errors: 65 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The probe answered its question over two runs: 3.14 builds, installs and
passes the whole suite. With the NoBlock alias gone in the previous
commit there is nothing left holding the cap at <3.14, and leaving it
there refuses installation for anyone on 3.14 -- a version OctoPrint 2.0
supports.

So raise requires-python to <3.15 and move 3.14 from the probe into the
real build matrix, where it gates like every other version rather than
being informational.

The probe itself moves up to 3.15, which is in beta. It is the same
arrangement: forced past the cap with --ignore-requires-python,
continue-on-error because a failure there is a version this package
declares unsupported. It doubles as a check on the previous commit --
3.15 is where PyImport_ImportModuleNoBlock actually disappears, so a
green 3.15 probe is the proof the swap was both needed and sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Raising requires-python to <3.15 means a 3.14 user can install the
plugin, and OctoPrint 1.11 caps at <3.14, so the only OctoPrint they can
be running is 2.0. That pairing had no coverage: both discovery legs ran
on 3.11.

The matrix now carries a python version per leg and adds 2.0 on 3.14.
Whether OctoPrint 2.0's own dependencies have 3.14 wheels yet is exactly
what this will tell us, and it is worth knowing before a user finds out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
package.json has no lockfile, so npm resolves within the caret ranges on
every run and a new eslint major is only noticed when the job breaks --
which is precisely how the @eslint/js mistake surfaced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
on_api_command is the plugin's only externally reachable entry point and
every branch of it is guarded by an OctoPrint permission, which is why I
left it uncovered earlier -- making those checks evaluate looked like it
needed a flask request context.

It does not. The plugin reads Permissions as a module attribute, so
swapping in a stub exercises the real branching, and stubbing
TranslateWorker keeps the tests from spawning threads or touching disk
while still capturing what the plugin decided to hand it.

Fourteen tests: FILES_UPLOAD gating translate and preview; the
after-translate downgrade ladder, where load needs FILES_SELECT, print
falls back to load without PRINT and to nothing without FILES_SELECT, and
printAndDelete is treated like print; shift coercion; settings patterns
reaching the worker; non-gcode rejection; preview always previewing; and
the test command starting nothing.

valid_file_type is stubbed as well -- the real one asks the plugin
manager which extensions are registered, and which extensions count as
gcode is OctoPrint's business rather than this plugin's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Submitting the same file with the same shifts while the first translate
is still running raised UnboundLocalError instead of reporting that it
was already in flight: the duplicate branch reads len(shifts), but shifts
is only bound in the branch that starts a worker. The user got a 500 and
the UI never received the "running" notification it was meant to show.

len(data['shifts']) is the count that branch was reaching for -- the
number of shifts requested, which is what the message reports.

Found while writing the API permission tests in the previous commit; the
two new tests cover the duplicate path and confirm that a request with
different shifts is still treated as new work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
It failed, and the reason is nothing this plugin controls: installing
OctoPrint 2.0 on 3.14 pulls a PyYAML with no 3.14 wheel, so pip builds it
from source and its setup.py dies on modern setuptools with
AttributeError: 'build_ext' object has no attribute 'cython_sources'.

The plugin itself is fine on 3.14 -- the gating py3.14 build job and the
3.15 probe both pass. So this leg becomes informational, like the version
probe, and its going green later is the signal that the combination has
become usable.

Worth recording plainly: this means nobody can actually run OctoPrint on
3.14 today, since 1.11 caps at <3.14 and 2.0's dependencies will not
install there. Raising requires-python to <3.15 does not unlock anyone
yet; it just stops this plugin from being the thing in the way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Job-level continue-on-error keeps the workflow run green but still marks
the job's check as failed, so the previous commit left a permanently red
entry in the PR's checks list for a job that provides no coverage at all
today. A red X nobody is expected to act on is worse than no job: it
teaches people to skim past red.

Instead the install steps are allowed to fail on 3.14 specifically, the
discovery checks are skipped when they do, and a notice says why. The job
goes green with an explanation rather than red with none, and the moment
OctoPrint 2.0 installs on 3.14 the checks start running for real with no
change to this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
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.

Cannot update from 0.3.0 to 0.3.2 (logs included)

2 participants