bug: hire-an-agent TOML editor corrupts existing [agents.X] block on re-run #886

Closed
opened 2026-04-16 15:36:03 +00:00 by dev-bot · 0 comments
Collaborator

Problem

Re-running disinto hire-an-agent <name> for an agent that already has a [agents.<name>] section in projects/<project>.toml corrupts the TOML file. The Python regex-based editor in lib/hire-agent.sh (invoked in Step 6 — Writing [agents.X] to ...) does not handle the case where the existing block is present in commented-out form or when it re-encounters its own prior output.

Repro

Starting state: projects/disinto.toml contains a clean [agents.dev-qwen2] block.

disinto hire-an-agent dev-qwen2 dev --local-model http://10.10.10.1:8081 --model unsloth/Qwen3.5-35B-A3B

After the re-run, projects/disinto.toml contains the following mess (observed today):

# DISABLED (dash in section name crashes load-project.sh — see issue)
# [agents.dev-qwen2]
base_url = "http://10.10.10.1:8081"
model = "unsloth/Qwen3.5-35B-A3B"
api_key = "sk-no-key-required"
roles = ["dev"]
forge_user = "dev-qwen2"
compact_pct = 60
poll_interval = 60
["dev"]
# forge_user = "dev-qwen2"
# compact_pct = 60
# poll_interval = 60
  • The # [agents.dev-qwen2] header stayed commented out (workaround from #862 era).
  • The base_url = ... line through poll_interval = 60 are NOT in any section — they attach to [monitoring] (the preceding table).
  • The line ["dev"] is a malformed TOML fragment (looks like the regex grabbed roles = ["dev"] and emitted the rest of that line as a standalone header).
  • The next lines are commented-out partial duplicates.

Result: tomllib.load() fails → both early-parse and late-parse paths in entrypoint.sh silently skip → PROJECT_NAME stays at the compose default → clone lands in wrong directory (see also #861).

Root cause

lib/hire-agent.sh writes the TOML section using a Python re.sub block (search shows regex patterns near line ~445–475). The regex:

  1. Does not recognize its own commented-out header (# [agents.X]) as "already present," so it creates a second incarnation while leaving the commented block in place.
  2. Emits the new block below the [monitoring] section instead of in a dedicated location, with no trailing newline/section anchor — subsequent keys attach to whatever section currently precedes them.
  3. The ["dev"] artifact suggests the regex matched on roles = ["dev"] and substituted only part of the line.

Fix

Use a proper TOML library (tomllib in read-only form already available; tomli_w or manual re-serialization for write) instead of regex. The editor should:

  • Parse the existing TOML with tomllib.
  • Update or insert the agents.<name> sub-table in the parsed structure.
  • Re-serialize the whole file.

This is robust to comments, section reordering, and repeated runs.

Acceptance

  • Re-running disinto hire-an-agent <name> for an existing agent produces a clean, idempotent TOML (no duplicate blocks, no orphan keys, no invalid ["dev"] artifacts)
  • tomllib.load() succeeds on the resulting file
  • Existing commented-out [agents.<name>] blocks are either cleaned up or left untouched — but NEVER produce mixed state
  • First-run (clean TOML) behavior unchanged

Affected files

  • lib/hire-agent.sh — Step 6 TOML writer (Python heredoc around line 440–475)

Context

Caught during today's dev-qwen2 re-hire after #862 landed. Had to manually patch projects/disinto.toml with a separate Python script to unblock. The re-hire scenario is not a hypothetical — idempotent re-runs are explicitly required by #800.

## Problem Re-running `disinto hire-an-agent <name>` for an agent that already has a `[agents.<name>]` section in `projects/<project>.toml` corrupts the TOML file. The Python regex-based editor in `lib/hire-agent.sh` (invoked in Step 6 — `Writing [agents.X] to ...`) does not handle the case where the existing block is present in commented-out form or when it re-encounters its own prior output. ## Repro Starting state: `projects/disinto.toml` contains a clean `[agents.dev-qwen2]` block. ``` disinto hire-an-agent dev-qwen2 dev --local-model http://10.10.10.1:8081 --model unsloth/Qwen3.5-35B-A3B ``` After the re-run, `projects/disinto.toml` contains the following mess (observed today): ```toml # DISABLED (dash in section name crashes load-project.sh — see issue) # [agents.dev-qwen2] base_url = "http://10.10.10.1:8081" model = "unsloth/Qwen3.5-35B-A3B" api_key = "sk-no-key-required" roles = ["dev"] forge_user = "dev-qwen2" compact_pct = 60 poll_interval = 60 ["dev"] # forge_user = "dev-qwen2" # compact_pct = 60 # poll_interval = 60 ``` - The `# [agents.dev-qwen2]` header stayed commented out (workaround from #862 era). - The `base_url = ...` line through `poll_interval = 60` are NOT in any section — they attach to `[monitoring]` (the preceding table). - The line `["dev"]` is a malformed TOML fragment (looks like the regex grabbed `roles = ["dev"]` and emitted the rest of that line as a standalone header). - The next lines are commented-out partial duplicates. Result: `tomllib.load()` fails → both early-parse and late-parse paths in `entrypoint.sh` silently skip → `PROJECT_NAME` stays at the compose default → clone lands in wrong directory (see also #861). ## Root cause `lib/hire-agent.sh` writes the TOML section using a Python `re.sub` block (search shows regex patterns near line ~445–475). The regex: 1. Does not recognize its own commented-out header (`# [agents.X]`) as "already present," so it creates a second incarnation while leaving the commented block in place. 2. Emits the new block below the `[monitoring]` section instead of in a dedicated location, with no trailing newline/section anchor — subsequent keys attach to whatever section currently precedes them. 3. The `["dev"]` artifact suggests the regex matched on `roles = ["dev"]` and substituted only part of the line. ## Fix Use a proper TOML library (`tomllib` in read-only form already available; `tomli_w` or manual re-serialization for write) instead of regex. The editor should: - Parse the existing TOML with `tomllib`. - Update or insert the `agents.<name>` sub-table in the parsed structure. - Re-serialize the whole file. This is robust to comments, section reordering, and repeated runs. ## Acceptance - [ ] Re-running `disinto hire-an-agent <name>` for an existing agent produces a clean, idempotent TOML (no duplicate blocks, no orphan keys, no invalid `["dev"]` artifacts) - [ ] `tomllib.load()` succeeds on the resulting file - [ ] Existing commented-out `[agents.<name>]` blocks are either cleaned up or left untouched — but NEVER produce mixed state - [ ] First-run (clean TOML) behavior unchanged ## Affected files - `lib/hire-agent.sh` — Step 6 TOML writer (Python heredoc around line 440–475) ## Context Caught during today's dev-qwen2 re-hire after #862 landed. Had to manually patch `projects/disinto.toml` with a separate Python script to unblock. The re-hire scenario is not a hypothetical — idempotent re-runs are explicitly required by #800.
dev-bot added the
backlog
priority
labels 2026-04-16 15:36:03 +00:00
dev-qwen self-assigned this 2026-04-16 15:56:10 +00:00
dev-qwen added
in-progress
and removed
backlog
labels 2026-04-16 15:56:10 +00:00
dev-qwen removed their assignment 2026-04-16 16:31:21 +00:00
dev-qwen removed the
in-progress
label 2026-04-16 16:31:21 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: disinto-admin/disinto#886
No description provided.