Repository navigation
Delphi 2.1.0: add Codex workspaces with branch-only inheritance - #80
Conversation
Review: bugs and suggested fixes
Merge conflictOnly High
Medium
Low / quality
Extensibility (adding OpenCode or another harness)
Suggestion: move these per-harness facts (native paths, protected files, folders to treat as foreign, a settings generator, a bootstrap command) into one registry entry per harness, for example Generated by Claude Code |
Re-review at
|
| # | Status |
|---|---|
| Merge conflict; 1 empty pushes; 2 folder rename; 4 branch plugins dropped; 9 global variables | Fixed (1, 2 and 4 re-run in the sandbox) |
| 5 sentence-matching awk | Replaced by the overrides; see the correction above |
| 8 copied content | Fixed |
| 3 Claude-only file in a shared bundle | Partly fixed: check.sh passes once the Codex workspace opts out, but sync now gets stuck (new #1) |
6 $DELPHI_ROOT for Codex |
Partly fixed: each skill gets a setup block and exec.sh routing, but it still relies on the model filling in paths; there's no hook |
7 safe_path |
Partly fixed: @ and + are allowed, spaces are still rejected |
| 10 size | Worse: sync.sh is 215 lines on main and 498 here; the PR is +2196/-357 |
New findings
- High (reproduced): a shared Claude-only file plus its opt-out deadlocks Codex refresh.
refresh()callswith_generated "$from", which renders with the branch'sworkspace.ymlbefore main is merged in. Scenario: one main PR adds.claude/commands/x.mdto a sharedworkspace-inherit/and addsopt-out: - .claude/commandstodefault-codex. This is the only ordercheck.shaccepts, because an opt-out added earlier fails as "nothing it inherits". Every refresh ofws/s.app.argos.default-codexthen fails with "has no mapping to codex", because the opt-out never reaches the branch. The e2e covers this only throughrender.sh, not through sync. Fix: use the opt-outs from both main's and the branch'sworkspace.ymlin that render, or skip files a harness can't map, with a notice. The translation function above would also remove most of these failures. - Low (reproduced):
reconstruct_recordshas dead code. It looks forthen entry=\$main_native/\$path; fiin historicalci/sync.sh, but that line only ever existed in646a452, never on main. Delete it. - Low (reproduced):
aligned_refsadds 3 artificial "align" commits on a folder move. One of them carries a whole main-shaped tree, and they reach ws/* history and, through proposals, main's. - Low (from reading the code): a refresh still writes several commits per real change ("prepare owned files", the merge, "sync generated"). This now happens only when something actually changed.
Extensibility
I added OpenCode in the sandbox with one descriptor and one template, and needed no edits to sync, check or new-workspace. That works. What's still left:
- the skill overrides, which the correction above addresses;
- a new skill metadata format still means editing the awk in
ci/harnesses.sh; - plugins, settings, MCP config and a SessionStart hook still exist only for Claude (
ci/plugins.shand.delphi/plugins.share Claude-only).
Generated by Claude Code
Third review at
|
| Item | Status |
|---|---|
BODY.<harness>.md replaced by a translation function per harness, skill_ref and a leak check |
Fixed, with gaps (see 1–2 below). The overrides are deleted. codex.sh sets skill_ref='$%s', the Claude identifiers live in claude-code.sh, and check.sh fails when one it knows about survives translation |
| Refresh deadlock (shared Claude-only file plus opt-out in the same main PR) | Fixed, reproduced through sync: refresh now merges main before rendering |
Dead reconstruct_records grep |
Fixed |
aligned_refs "align" commits |
Fixed, reproduced: refresh publishes one merge with parents (from, main), and propose uses (main, owner) |
| Several commits per refresh | Fixed: one delphi: refresh commit per real change |
Earlier fixes: no pushes on unrelated main commits, folder rename with the same short-name, plugins chosen on a branch |
Still fixed (re-run) |
| Adding OpenCode | Fixed, reproduced: one ci/harnesses/opencode.sh plus a template needed no other edits. Plugins and settings are still Claude-only |
$DELPHI_ROOT for Codex |
Partly fixed, unchanged: setup text plus exec.sh, still no hook |
Spaces in safe_path |
Not fixed |
sync.sh size |
Partly fixed: 485 lines (215 on main); the PR is +2464/-358 |
New findings
-
Medium (reproduced): the leak check only catches identifiers on a hand-written list (
claude_identifiersinci/harnesses/claude-code.sh). These all reached Codex output with exit 0:~/.claude/settings.jsonand$HOME/.claude/CLAUDE.md, because a.claudepreceded by/is excluded from the match;subagent_type=Explore, WebFetch,claude plugin install,slack_send_message,ultrathink.
It also reports only the first hit on each line, and its line numbers count lines in the translated output (including the setup text), not in the source SKILL.md.
-
Medium (reproduced): some rewrites are wrong.
- Every bare
CLAUDE.mdbecomesAGENTS.md, including inside code blocks and when it means a code repo's own file ("read the code repo's CLAUDE.md",grep -n rules CLAUDE.md). Only path-qualified references likeworktrees/argos/CLAUDE.mdare kept. - The slash rewrite loop matches again at the start of the text left after each match, so
/tmp/commitbecomes/tmp$commit.
Suggest skipping code blocks and inline code, and only rewriting
/<skill>at a word boundary that no/or word character comes before. - Every bare
-
Low (from reading the code):
harness_skill_names(ci/harnesses.sh) takes every SKILL.md folder name undersoftware/. That includes workspace-owned and plugin skills, and generic names likereviewandworkflow. A workspace adding a generically named skill changes/wordrewriting in every shared skill. Suggest limiting it to the skills that workspace actually inherits. -
Low (reproduced): "Use the pull_request_read tool" becomes "Use the PR-reading tool tool".
-
Low (from reading the code): the mapped Claude identifiers (
pull_request_read,playwright-cdp, thedelphimod bullet,/ws-delphi) are hard-coded in theclaude_identifiersawk, not kept as data. Each new one means editing the awk and adding a value to every other harness file. Suggest a data table, one identifier per row with a value for each harness, that both the translator and the leak check read.
Not checked: the new requirement in apply_inheritance that every path in a branch's workspace.generated is in the trusted records. The normal flow holds, but a harness switch made on the branch alone wasn't tested.
Generated by Claude Code
|
On the latest review, two qualifications:
For 2, skipping all inline code would also skip valid backticked skill calls such as |
Review of
|
|
Addressed the simplification request in 08ea327. Removed Validation: all 193 sandbox checks, focused harness tests, ShellCheck and repository validation pass. Existing prepared workspace output is byte-identical. |
Changes
Add Argos's default Codex workspace. CI merges main into each workspace tree, prepares inheritance using the merged harness/configuration, validates ownership, and publishes one changed commit. Canonical Claude skills stay on main; inherited native copies and their ledger live only on workspace branches.
Each harness descriptor supplies native paths, a translator and optional settings generation. Skill references use the receiving workspace's catalog. Claude skill bytes and existing workspace configuration stay intact.
Diagram
One refresh: merge main → prepare inheritance for the merged harness → commit changed files → push.
Blue steps run the shared native preparation pipeline. All preparation happens in temporary CI files.
1. The workspace branch receives one finished result
--- config: theme: base htmlLabels: false fontSize: 16 themeVariables: primaryColor: '#f3f4f6' primaryTextColor: '#111827' primaryBorderColor: '#64748b' nodeTextColor: '#111827' clusterBkg: '#f8fafc' clusterBorder: '#94a3b8' titleColor: '#111827' lineColor: '#64748b' edgeLabelBackground: '#f3f4f6' flowchart: htmlLabels: false useMaxWidth: true subGraphTitleMargin: top: 10 bottom: 18 --- flowchart TD CHANGE["Push to main or ws/name"] --> SYNC["ci/sync.sh: refresh()"] subgraph CI["Temporary CI workspace"] MERGE["1. Merge main into workspace tree"] RENDER["2. stage_inheritance()"] APPLY["3. apply_inheritance()"] COMMIT["4. Create publish commit if changed"] MERGE --> RENDER --> APPLY --> COMMIT end SYNC --> MERGE COMMIT --> PUSH["5. Push ws/name"] style RENDER fill:#eff6ff,stroke:#2563eb,color:#111827,stroke-width:2pxStep 2 reads the merged workspace.yml. Step 3 verifies ownership and installs the prepared bytes,
ledger and native settings; main's runtime is added before publication. Owned-file conflicts are
reported. An unchanged tree leaves the workspace ref untouched. Prior source output may be replayed
to verify ownership during application; there is no current-source render before the merge.
refresh()→with_inherited()→inherited()refresh()→ shared preparation below → Claude descriptor.claude/skills/files2. Every harness uses these shared function calls
Solid arrows are calls. The workspace harness selects the descriptor used for path mapping and content.
--- config: theme: base htmlLabels: false fontSize: 16 themeVariables: primaryColor: '#f3f4f6' primaryTextColor: '#111827' primaryBorderColor: '#64748b' nodeTextColor: '#111827' clusterBkg: '#f8fafc' clusterBorder: '#94a3b8' titleColor: '#111827' lineColor: '#64748b' edgeLabelBackground: '#f3f4f6' flowchart: htmlLabels: false useMaxWidth: true subGraphTitleMargin: top: 10 bottom: 18 --- flowchart TD STAGE["ci/sync.sh: stage_inheritance()"] --> RENDER["ci/render.sh: render_workspace()"] RENDER --> SELECT["ci/inherit.sh: inherited_files()"] SELECT --> BUNDLE["ci/sources.sh: bundle_files()"] BUNDLE --> PATH["source_path(): map destination"] RENDER --> FILE["ci/harnesses.sh: harness_file()"] FILE --> CONVERT["Descriptor: translate_skill()"] style PATH fill:#eff6ff,stroke:#2563eb,color:#111827,stroke-width:2px style CONVERT fill:#eff6ff,stroke:#2563eb,color:#111827,stroke-width:2pxsource.ymlselects the input format (Claude by default); the workspace'sharness:selects theoutput. The renderer hashes the exact prepared bytes into
workspace.generated. Non-skill helperscripts keep their bytes.
apply_inheritance()checks these records against trusted prior output,protects owned/retired files and copies the prepared output into the merged workspace tree.
.claude/skills/commit/SKILL.md.agents/skills/commit/SKILL.md/commit,CLAUDE.md,allowed-tools$commit,AGENTS.md, omitted unsupported fieldbash scripts/x.sh.delphi/exec.shinvocationConversion happens during CI preparation. The harness reads finished native files on its branch;
the command wrapper runs later, when a helper is invoked. Qualified paths and filenames in inline or fenced code stay literal. Bare instruction names in prose map to the target instruction file.
3. A new harness plugs into the descriptor interface
--- config: theme: base htmlLabels: false fontSize: 16 themeVariables: primaryColor: '#f3f4f6' primaryTextColor: '#111827' primaryBorderColor: '#64748b' nodeTextColor: '#111827' clusterBkg: '#f8fafc' clusterBorder: '#94a3b8' titleColor: '#111827' lineColor: '#64748b' edgeLabelBackground: '#f3f4f6' flowchart: htmlLabels: false useMaxWidth: true subGraphTitleMargin: top: 10 bottom: 18 --- flowchart TD CONFIG["workspace.yml: harness"] -. selects .-> LOAD["Load ci/harnesses/HARNESS.sh"] FILE["harness_file(harness, file)"] --> LOAD LOAD -->|claude-code| CLAUDE["claude-code.sh"] LOAD -->|codex| CODEX["codex.sh"] LOAD -->|future| NEW["future.sh: add descriptor"] CLAUDE --> CAT["translate_skill(): cat"] CODEX --> CONVERT["translate_skill(): convert"] NEW --> CUSTOM["translate_skill(): your conversion"] style NEW fill:#e6f4ea,stroke:#1a7f37,color:#111827,stroke-width:2px style CUSTOM fill:#e6f4ea,stroke:#1a7f37,color:#111827,stroke-width:2pxAdd a descriptor and its instruction template, implement its translator, then select the harness in
workspace.yml. Shared sync, rendering, validation and creation use the registered interface.
source_path()→harness_field()harness_file()→translate_skill()harness_settings()→ registered generatortools/new-workspace.sh→harness_field()Creation, proposals and runtime
tools/new-workspace.shseeds owned defaults once through the same path/contentadapters, generates settings and opens a main PR. Inherited copies and ledgers stay off main.
propose()merges into a temporary main candidate, verifies and removes managedinheritance, then regenerates settings and runs
ci/check.sh. Owned native files and opted-outcopies return to main in their existing format.
ci/check.shusesci/render.sh --check, plus.delphi/names.shand.delphi/version.sh. Validation may independently render disposable output..delphi/exec.sh→.delphi/paths.sh→ helper; Claude retains itsSessionStart hook. Workspace setup/switch/parking and code worktrees keep their existing layout.
Testing
Notes
Runtime release 2.1.0 adds the shared command bootstrap on top of main's 2.0.0. CI creates ws/s.app.argos.default-codex after merge. Existing native workspace configuration and the Argos development workflow remain intact. Shared paths and opt-outs support + and @; spaces and control characters remain unsupported and are documented. Owned filenames are unaffected.
The ownership regression also covers #81, which remains open for review. Ownership is validated during application using trusted prior source output, with historical replay when required. Current inheritance generation happens after the merge. Codex uses per-command bootstrap; this setup has no Codex SessionStart hook. Host provisioning stays separate. Tests ran on Linux; no live model or Argos application smoke test ran. Real Claude mod tests were skipped because its executable is unavailable.
Checklist