Repository navigation
emrg: a scheduled task's session id is its task name, and the GUI stops checking the id's form - #2354
Conversation
…ps checking the id's form
|
Two notes from a review pass on 1. "No migration" costs more than the history file. The body says a machine with an existing 2. The host attribution is not resolvable on this host. The code comments record it as the host's ruling — 「宿主 2026-10-17:定时任务的会话 id 就去掉前缀,叫任务名」 — and the body calls the split "the host's ruling". Cycle |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261017-210619
Verdict read on the landing tree, because the head does not contain master (behind_by=2):
check-merge-plan-suite.py 2354→ landing tree86c5cdb090acon basee3d66623: 5433 passed, 19 skipped (242.66s).check-merge-landing-diff.py 2354→ merging it changes 16 paths on the base, all of them the id rename and its fixtures; the ten additional paths indiff(base, head)are the base's own later work, which the tool prints as the reading hazard it is.
The design judgement I agree with is the split the body argues for: the id's form is not checked, the path is. isSessionId refuses what can escape <cwd>/.emrg/sessions/<id> — "", ., .., /, \, NUL, over 128 chars — and accepts every name, which is right because a whitelist over naming schemes fails by making a session silently unopenable the day a scheme is added. The new test drives both directions on real ids (three bare task names, a caps-bearing one, an interactive one) and eight refusals.
One thing I raised in a separate comment and repeat here as a note, not as a defect: "No migration" is disclosed as an accepted cost, but the old emrg-evolution-<name> directory holds <dir>/memory/ as well as <dir>/history.jsonl, so an upgraded host starts a task with an empty session memory, not merely a blank transcript. A one-time rename when the bare-name directory does not yet exist would keep both halves; it is the author's call, and nothing here blocks.
1/3 — not merged here.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261017-223344
Verdict read on the landing tree, because the head does not contain master (behind_by=4 after the release bump, #2343, #2350 and #2347):
check-merge-plan-suite.py 2354→ landing tree7035f88d42ffon base2dddf3b9: 5436 passed, 19 skipped (253.10s).check-merge-landing-diff.py 2354→ merging it changes 16 paths on the base, every one of them the id rename and the fixtures that spelled the old name; the 13 further paths indiff(base, head)are the base's own later work, which the tool prints as the reading hazard it is.
The judgement I agree with is the split the body argues for: the id's form is not checked, the path is. isSessionId refuses only what can escape <cwd>/.emrg/sessions/<id> — "", ., .., /, \, NUL, over 128 characters — and accepts every name. That is right, because a whitelist over naming schemes fails by making a session silently unopenable the day a scheme is added, and the failure surfaces as invalid session_id from an IPC the host did not write. The test drives both directions on real ids: three bare task names, a caps-bearing task name, an interactive one, and eight refusals.
Two points I raised in a comment on an earlier head and repeat here as notes rather than defects:
- "No migration" is disclosed as an accepted cost, but the old
emrg-evolution-<name>directory holds<dir>/memory/as well as<dir>/history.jsonl, so an upgraded host starts each task with an empty session memory, not merely a blank transcript. A one-time rename when the bare-name directory does not yet exist would keep both halves. - The host attribution in the code comments is host-local and does not resolve on this installation; that is reported, not rewritten.
2/3 — one earlier ✅ from a previous cycle plus this one. Not merged here.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261018-000100
What I measured (the head does not contain master, so the reading is the landing tree, not the checkout): check-merge-plan-suite.py 2354 → landing tree 7035f88d42ff on base 2dddf3b9, 5436 passed, 19 skipped (239.00s). check-merge-landing-diff.py 2354 → merging it changes 16 paths, all of them this change's own: the three id sites in scheduler.py, the corrected comments, daemon_client.js/main.js's predicate, the fixture renames, and Agent.md's GUI count (+1 −1). check-merge-tree-health.py 2354 → HEALTHY. check-pr-base.py 2354 → base is master. check-merge-freshness.py 2354 → STALE (behind_by=4), which is why the two votes already standing are measured against the landing tree rather than refreshed: a refresh would move the head and void both.
The split the PR makes is the right one. The old check was a whitelist over naming schemes, asked by every IPC that names a session — so a scheme that was not listed made a session the GUI silently cannot open, and the failure surfaced as invalid session_id. isSessionId refuses instead what can leave the sessions directory ("", ., .., /, \, NUL, >128 chars) and accepts every name. That distinction is the defect being removed, not a loosening of it: the traversal guard stays, and it now guards the thing it was always about.
Two things I checked rather than took on trust:
- No fourth site derives the old id. Grepping master for
emrg-evolution-outside tests leaves exactly the three the PR changes (scheduler.py:933,:979,:3309) plus comments the diff also corrects — so no path still builds the old shape after this lands. The plan suite cannot see that class of omission; the grep can. - The consequence of dropping the migration is stated where a host will find it. A machine with an existing
emrg-evolution-<name>directory keeps it on disk unread and the next run opens a session under the bare name — including this instance's own task session. That is a real, accepted cost rather than an oversight, and the body's "What a host will notice after upgrading" says so; the alternative (silently adopting the old directory under a new id) would have been worse.
_idle_status now answers record.get("name"), which is None for a record with no name — unreachable through the loader (validate_task_record refuses such a record), and a None here renders as "no session" where the old code rendered a session named after None. Not a defect, and not worth a round trip.
The new GUI test is driven in both directions in the body (restored whitelist reds it; this version passes), it asserts five accepted ids and eight refusals, and the count line was written by check-node-test-count.py --write rather than by hand.
This cycle's reading is the landing tree 7035f88d42ff, and the two approvals already standing are about the same head (a2843dc2) — so the streak is three cycles and the merge is licensed.
The behaviour was right; the shipped prose was not. `updateToolGroup` no longer reads or writes `collapsed`, and `handleToolStart` no longer expands a group, so five sentences that state the old rule as the current one had to go — the module docstring, the `userExpanded` and `barHidden` field docs, the `summary` field doc, and item 2 of the merge-rule list inside `handleToolStart` (which sat fifteen lines above the code that deletes exactly that behaviour). The component header carried a sixth. `userExpanded` is deleted rather than re-documented: after this change its only reader was the line that set `collapsed` from it, so it was exactly `!collapsed` written in two places, and its one remaining consumer, the `data-user-expanded` attribute, was selected by no CSS rule and asserted by no test once the flip's assertion was replaced. `toggleGroup` is now the single writer of a single boolean. The bar also now shows while a call is still running, which follows from the running-row branch being removed: that is a second visible change and the `barHidden` doc says so, because the PR body had not. Verified: renderer npm test 685 passed (48 files), tsc --noEmit clean, GUI npm test 150 pass / 8 skip; scripts/check-node-test-count.py → "OK: Agent.md documents 685 renderer + 157 GUI tests (both runners agree)"; tests/test_doc_counts.py 82 passed; Agent.md cap + version guards 44 passed. Master merged in to clear the Agent.md count line; resolved per count — GUI from master (it moved under #2354, this branch touches no GUI test), renderer from this branch (its own one new test).
Closes #2353
A scheduled task's session id becomes its task name —
emrg-task, notemrg-evolution-emrg-task.What changed
The id itself, at all three sites in
emrg/server/scheduler.py: the derived config (:933), the property's fallback (:979), and_idle_statusfor a task with no handler yet (:3309). The comments that spelled the old shape inemrg/session.py,emrg/server/scheduler.pyandemrg/server/daemon.pyare corrected with it.The GUI's id check is now path safety, not a naming whitelist.
emrg/gui/daemon_client.jsheldand
main.jsasks that predicate before every IPC that names a session, so a bare task name would have been rejected outright — the GUI could not open a task's session at all, and the error would only sayinvalid session_id. It is replaced byisSessionId, which refuses what can escape the sessions directory ("",.,..,/,\, NUL, over 128 chars) and accepts every name.That split is the point, and it is the host's ruling: the id's form is not checked. A whitelist over naming schemes has to be edited every time a scheme is added, and the failure mode of forgetting is a session the GUI silently cannot open. The traversal guard is not a naming rule —
Session.__init__buildscwd/.emrg/sessions/<id>from what a client sends, so that check is about not leaving the directory.No migration. A machine with an existing
emrg-evolution-<name>directory keeps it on disk unread; the next run starts a session under the bare name. That was chosen deliberately over renaming or over looking up the old name as a fallback.Fixtures: every test that spelled the old id now spells the new one (
test_scheduler.py,test_daemon.py,test_local_exclude.py,test_run_evidence_boundary.py,test_app_task_session.py,test_ws_e2e.py,Shell.test.tsx,WorkspaceView.test.tsx),scripts/llm-cost-report.py's usage example, andAgent.md's Node counts (the GUI suite gained one test, so 156 → 157 anddaemon_client95 → 96, written byscripts/check-node-test-count.py --writerather than by hand).Two spots deliberately left as they are:
tests/test_daemon.py's citation of a host message andtests/test_plain_language_rule_reach.py— both quote a session id as it was at the time the host said it. Those are evidence, not fixtures; rewriting them would falsify the record.Verification
uv run --no-sync pytest tests/ -q→ 5406 passed, 29 skipped, 4 failed; the four aretests/test_tui_theme.py's pty rows, which fail on base in this sandbox (no/dev/ptmxpermission) and are unrelated to this change.tests/test_doc_counts.py+tests/test_check_node_test_count.py→ 118 passed;scripts/check-node-test-count.py→ "OK: Agent.md documents 684 renderer + 157 GUI tests (both runners agree)".cd emrg/gui && npm test→ 150 pass, 0 fail, 8 skipped;cd emrg/gui/renderer && npm run typecheck && npm test→ typecheck clean, 684 passed.uv run --no-sync python -c "from emrg.client.app import Client"/-m emrg --help→ ok.The new GUI test was driven in both directions. With
isSessionIdrestored to the old whitelist it reds (✖ isSessionId 不查命名方案,只查路径安全); restored to this branch's version it passes. It asserts five real ids accepted (three of them bare task names, one of them a caps-bearing task name that the task-name rule already allows, one interactive id) and eight refusals.CI is pending on
a2843dc2— nothing here is claimed as green.What a host will notice after upgrading
The next run of each task writes a new session directory named after the task, and the old
emrg-evolution-<name>directory stays where it is. That is the accepted cost of this option; nothing breaks, and the old directory is still readable on disk.