ESCO classification: after fetch_esco writes a new revision, every listing save in a fresh process fails #535
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#535
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, readingjobs/esco.pyandfetch_esco, and checking the loader against two revisions in one directory.Checked again on 2026-10-01 against
mainat0a5cad587: still present. The callers are now atjobs/models.py:974(JobPosting.save),jobs/models.py:993(isco_name) andjobs/forms.py:749(esco.suggestions());jobs/esco.py,fetch_esco.pyand the other locations have not moved.manage.py fetch_esco --revision <new>writes the new revision's two files beside the old ones and then only prints a warning asking for the old ones to be deleted. From that moment the loader refuses to read either, and because the refusal is an exception,lru_cachedoes not keep it: every call in a process that had not already loaded the classification raises again. The upgrade the wiki describes, run withdocker compose execagainst a running instance, therefore breaks saving listings until somebody deletes the old files, and nothing reports the state at start-up.jobs/esco.py:108-122(data_file) andjobs/esco.py:296-310(skills_file), which raiseRuntimeErroron two files;jobs/esco.py:125-126(classification,lru_cache) andjobs/esco.py:313-322(_member, which callsskills_file()on every read, uncached);jobs/management/commands/fetch_esco.py:182-193, which lists the old files inothersand only warns; the callersjobs/models.py:971(JobPosting.save→esco.code_for),jobs/models.py:990(isco_name, shown bytemplates/jobs/posting_detail.html:122),resume/models.py:184(Skill→esco.skill_for),jobs/forms.py:752andapplications/forms.py:177(esco.suggestions()).esco-1.2.1.jsonandesco-1.2.2.jsonboth indata/,esco.code_for("Software developer")raisesRuntimeError: two ESCO classifications in … delete the one being replaced before starting Postuloon every call, and so doesesco.suggestions()(checked against the snapshot). Gunicorn retires each worker after about 500 requests (docker/Dockerfile:354), so within the window new workers answer 500 on adding or editing a listing, on the listing and application forms that offer titles, on the detail page of a listing that carries a code, and on the API and capture paths that save a listing. Already-running processes keep the old revision, except that_memberchecksskills_file()on every read, so they too fail on the first skill lookup in a language they have not read yet. The management commands and the scheduler fail the same way once restarted.fetch_escodelete the files inothersitself once both new files are written and validated (it already validates both halves before writing anything), so the directory goes from one revision to one revision with no window; keep the loader's refusal for a directory somebody filled by hand. Add a Django system check that reports two revisions of either file, socheck --deployat start-up names the state instead of the first save.Done when
tests/test_fetch_esco.py: a run with--revisionover a directory holding the previous revision's two files leaves only the new two.tests/test_esco.py: a system check reports an error when twoesco-*.json(or twoesco-skills-*.zip) files are present.