Skip to content

emrg: a session id the filesystem cannot name reaches mkdir and fails as an OS error #2598

Closed

Description

@pm25coder

A session id the filesystem cannot name is accepted by the predicate that guards it, and the failure that follows names the operating system instead of the name.

What happens

Session.create_with_id builds <agent>/.emrg/sessions/<id> and mkdirs it. On Windows a directory cannot be named after a reserved device, so the mkdir throws:

is_safe_session_id('con')     -> True
Session.create_with_id('con', <agent>)
NotADirectoryError: [WinError 267] The directory name is invalid:
  <agent>\.emrg\sessions\con

CON, con.txt and com1 fail the same way; nul gets one step further and fails on the memory subdirectory (FileNotFoundError [WinError 3]). Measured on Windows Server 2022, master 6ddb7cc, with emrg.__file__ printed by the probe.

A second shape fails without raising: Windows strips trailing spaces and dots from a name, so an id like a or a. names a different directory than the id says. Measured: .. mkdir'd "successfully" (it resolved to the parent) and the first write into it raised FileNotFoundError.

Why the predicate is the place

An id reaches the sessions directory through two doors, and both already ask emrg.session.is_safe_session_id:

  • an inbound client message, via Daemon._refuse_an_unsafe_session_id, which covers the eleven _get_or_create_session callers and the task door;
  • server-side creation, via _get_or_create_session and the sub-session slot (Session.create_with_id, two production call sites).

So a name the platform cannot create as a directory passes both doors and fails later, deep in the filesystem. The host-visible symptom is a session that does not save or a turn that dies; the message names WinError 267, which says nothing about the name being the problem.

The predicate already refuses what no filesystem accepts (\0) and what escapes the directory (/, \, ., .., a drive), on the ground that an id reaches a path on whichever platform runs it. A reserved device name is the same kind of refusal: it names no directory on the platform that can receive it.

What must not change

  • My Task (spaces), 中文任务 (CJK) and existing ids like emrg-task and s_260727_1103_866d create normally today and must keep creating.
  • console, con-artist, com10 and lpt0 are not reserved and must stay accepted: the documented set is CON, PRN, AUX, NUL, COM1–COM9, LPT1–LPT9 (and the same followed by an extension). COM0 and LPT0 are not in it.
  • A session directory already on disk must still load.

What the fix must satisfy

  1. is_safe_session_id refuses a reserved device name — case-insensitively, and with any extension, i.e. judged on the base name before the first . — and refuses an id whose last character is a space or a dot.
  2. The refusal is platform-independent, on the predicate's existing ground: an id reaches a path on whichever platform runs it, and an id that names nothing on one of them is not portable.
  3. Session.create_with_id raises a named error before a directory is attempted, so no filesystem error reaches the caller, and the message says which rule the name broke.
  4. The daemon reports this reason separately from a path escape: a device name is one path component, so the existing (a session id is one path component) sentence is false about it.
  5. Tests in both directions: the four names above stay accepted, con/CON/con.txt/nul/com1 and a trailing space or dot are refused on both CI legs, and the mutation that deletes the device-name rule reddens the new tests.

Pointers

  • emrg/session.py — _NAME_SEPARATORS, is_safe_session_id, Session.create_with_id
  • emrg/server/daemon.py — _refuse_an_unsafe_session_id (two doors, one writer)
  • tests/test_session_delete_path.py — the existing two-direction pin of the predicate

Activity

  1. how2how2how2-arch commented on Oct 26, 2026

    @how2how2how2-arch
    Collaborator

    Handled by #2599

  2. pm25coder commented on Oct 26, 2026

    @pm25coder
    CollaboratorAuthor

    Handled by #2600

  3. pm25coder commented on Oct 26, 2026

    @pm25coder
    CollaboratorAuthor

    Correction to the line above: #2600 was withdrawn unmerged, so it is not this issue's handler.

    It was opened 12 minutes before #2599 and carried a narrower version of the same fix (the reserved-device-name and trailing space/dot rule in the session-id predicate). #2599 carries the same rule through a single session_id_refusal_reason() producer, so it also feeds the daemon's refusal message, Session.delete's log line and create_with_id. One issue is finished by exactly one PR, so #2600 was closed and #2599 kept.

    #2599 is the PR that closes this issue — the "Handled by #2599" line is the one that holds.

    For the record, the reading behind the rule, taken on master 6ddb7cc: con / CON / con.txt / com1 / aux / prn / nul / lpt9 all pass is_safe_session_id() while create_with_id() fails on them with NotADirectoryError [WinError 267] (nul → FileNotFoundError [WinError 3]); My Task, 中文任务 and emrg-task create normally; a and a. are trimmed by the filesystem, so the directory no longer equals the id.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions