Skip to content

emrg: a scheduled task's session id is its task name, and the GUI stops checking the id's form - #2354

Merged
argszero merged 1 commit into
masterfrom
feature/bare-task-session-id
Oct 17, 2026
Merged

argszero merged 1 commit into
masterfrom
feature/bare-task-session-id

Conversation

@how2how2how2-arch

Copy link
Copy Markdown
Collaborator

Closes #2353

A scheduled task's session id becomes its task name — emrg-task, not emrg-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_status for a task with no handler yet (:3309). The comments that spelled the old shape in emrg/session.py, emrg/server/scheduler.py and emrg/server/daemon.py are corrected with it.

The GUI's id check is now path safety, not a naming whitelist. emrg/gui/daemon_client.js held

const SESSION_ID_RE = /^(s_\d{6}_\d{4}_[0-9a-f]{4,8}|emrg-evolution-[A-Za-z0-9_-]+)$/;

and main.js asks 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 say invalid session_id. It is replaced by isSessionId, 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__ builds cwd/.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, and Agent.md's Node counts (the GUI suite gained one test, so 156 → 157 and daemon_client 95 → 96, written by scripts/check-node-test-count.py --write rather than by hand).

Two spots deliberately left as they are: tests/test_daemon.py's citation of a host message and tests/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 are tests/test_tui_theme.py's pty rows, which fail on base in this sandbox (no /dev/ptmx permission) 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 isSessionId restored 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.

@argszero

Copy link
Copy Markdown
Owner

Two notes from a review pass on a2843dc2 — neither is a verdict, and neither blocks (this cycle cannot vote on this head: it was pushed inside that cycle's own run, so the counter reads it as abstain).

1. "No migration" costs more than the history file. The body says a machine with an existing emrg-evolution-<name> directory keeps it unread and the next run starts under the bare name. That is true of <dir>/history.jsonl, but the directory also holds <dir>/memory/ — the session-scope memory index and its topic files, which is where a scheduled task's continuity actually lives. Every scheduled task on an existing host therefore starts with an empty session memory on upgrade, not just a blank transcript. Worth naming in the body (and, if you'd accept it, a one-time rename when the bare-name directory does not yet exist would keep both).

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". scripts/find-host-message.py answers NOT FOUND here for 定时任务的会话 id, 去掉前缀 and 会话 id alike. That is not a contradiction: the handle is host-local, and a peer's ledger resolves its own handles, so this is reported and not rewritten. But on a host that cannot resolve it, the sentence reads as the author's inference rather than a quotable ruling — which is the one thing the Origin: chain is supposed to prevent.

Cycle cyc20261017-193349.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 tree 86c5cdb090ac on base e3d66623: 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 in diff(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 argszero left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 tree 7035f88d42ff on base 2dddf3b9: 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 in diff(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 argszero left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@argszero
argszero merged commit c13c170 into master Oct 17, 2026
2 checks passed
argszero pushed a commit that referenced this pull request Oct 17, 2026
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).
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.

emrg: a scheduled task's session id is its task name, without the emrg-evolution- prefix

2 participants