Skip to content

feat(devcontainer): portable multi-container flow with sidecar workspace#18

Open
FlexNetOS wants to merge 2 commits intomainfrom
feat/portable-multicontainer-devcontainer
Open

feat(devcontainer): portable multi-container flow with sidecar workspace#18
FlexNetOS wants to merge 2 commits intomainfrom
feat/portable-multicontainer-devcontainer

Conversation

@FlexNetOS
Copy link
Copy Markdown
Owner

@FlexNetOS FlexNetOS commented May 3, 2026

Adds an opt-in docker-compose flow so any sibling project can be opened inside a fresh agent_harness container, with prompt-nexus auto-attached as a sidecar workspace. The legacy single-container devcontainer.json remains the default for harness-on-harness work and is unchanged.

Phase 1 — compose + persistent fixes baked into the image

  • .devcontainer/docker-compose.yml: harness + prompt-nexus services on ah-net network, named volumes for harness deps preserved, target project + prompt-nexus + ~/.config bind-mounted from host.
  • .devcontainer/.env.example: host-path template documenting unified ~/.config layout across Linux, macOS, WSL, and Docker Desktop on Windows. Windows alignment via setx + robocopy is one-time per host.
  • .devcontainer/Dockerfile: /etc/profile.d/ecc-env.sh unsets GITHUB_TOKEN/GH_TOKEN on every interactive shell so gh reads stored auth from the bind-mounted ~/.config/gh, and anchors XDG_CONFIG_HOME to ~/.config. System-level commit.gpgsign=false so containers do not fail commits from a host signing key path that does not resolve in Linux.
  • .devcontainer/postCreate.sh: gh auth probe + final next-steps banner.

Phase 2 — environment validator + runbook

  • scripts/hooks/session-start.js: validateDevcontainerEnvironment() step gated on ECC_DEVCONTAINER_VALIDATION (set only by the compose environment block, never by the legacy flow). On the compose path, it checks /.dockerenv, target workspace mount, /home/node/.config/gh hosts.yml, commit.gpgsign=false, and XDG_CONFIG_HOME alignment. If any check fails, prepends a banner to additionalContext. Always exits 0.
  • tests/hooks/session-start-devcontainer.test.js: 4 tests covering no-op behaviour when the gate is off, banner emission on misconfiguration, and stderr logging.
  • docs/RUNBOOKS/portable-devcontainer.md: usage runbook with one-time Windows ~/.config alignment, per-project bring-up commands, troubleshooting, and rationale.
  • docs/architecture/adr/0005-portable-multicontainer.md: supersedes the no-compose stance of ADR 0004 specifically for the workspace- sidecar use case. Documents the named-volume invariant and the hard rule that MCP topology stays unchanged (do NOT containerize MCP servers into compose services).

MCP topology is intentionally unchanged: MCPs remain stdio children of claude inside the harness service. The sidecar is a workspace artifact, not a protocol peer. ADR 0004's MCP rationale still holds for the protocol layer.

What Changed

Why This Change

Testing Done

  • Manual testing completed
  • Automated tests pass locally (node tests/run-all.js)
  • Edge cases considered and tested

Type of Change

  • fix: Bug fix
  • feat: New feature
  • refactor: Code refactoring
  • docs: Documentation
  • test: Tests
  • chore: Maintenance/tooling
  • ci: CI/CD changes

Security & Quality Checklist

  • No secrets or API keys committed (ghp_, sk-, AKIA, xoxb, xoxp patterns checked)
  • JSON files validate cleanly
  • Shell scripts pass shellcheck (if applicable)
  • Pre-commit hooks pass locally (if configured)
  • No sensitive data exposed in logs or output
  • Follows conventional commits format

Documentation

  • Updated relevant documentation
  • Added comments for complex logic
  • README updated (if needed)

Open in Devin Review

FlexNetOS and others added 2 commits May 3, 2026 17:59
Adds an opt-in docker-compose flow so any sibling project can be opened
inside a fresh agent_harness container, with prompt-nexus auto-attached
as a sidecar workspace. The legacy single-container devcontainer.json
remains the default for harness-on-harness work and is unchanged.

Phase 1 — compose + persistent fixes baked into the image
- .devcontainer/docker-compose.yml: harness + prompt-nexus services on
  ah-net network, named volumes for harness deps preserved, target
  project + prompt-nexus + ~/.config bind-mounted from host.
- .devcontainer/.env.example: host-path template documenting unified
  ~/.config layout across Linux, macOS, WSL, and Docker Desktop on
  Windows. Windows alignment via setx + robocopy is one-time per host.
- .devcontainer/Dockerfile: /etc/profile.d/ecc-env.sh unsets
  GITHUB_TOKEN/GH_TOKEN on every interactive shell so gh reads stored
  auth from the bind-mounted ~/.config/gh, and anchors XDG_CONFIG_HOME
  to ~/.config. System-level commit.gpgsign=false so containers do not
  fail commits from a host signing key path that does not resolve in
  Linux.
- .devcontainer/postCreate.sh: gh auth probe + final next-steps banner.

Phase 2 — environment validator + runbook
- scripts/hooks/session-start.js: validateDevcontainerEnvironment()
  step gated on ECC_DEVCONTAINER_VALIDATION (set only by the compose
  environment block, never by the legacy flow). On the compose path,
  it checks /.dockerenv, target workspace mount, /home/node/.config/gh
  hosts.yml, commit.gpgsign=false, and XDG_CONFIG_HOME alignment. If
  any check fails, prepends a banner to additionalContext. Always
  exits 0.
- tests/hooks/session-start-devcontainer.test.js: 4 tests covering
  no-op behaviour when the gate is off, banner emission on
  misconfiguration, and stderr logging.
- docs/RUNBOOKS/portable-devcontainer.md: usage runbook with one-time
  Windows ~/.config alignment, per-project bring-up commands,
  troubleshooting, and rationale.
- docs/architecture/adr/0005-portable-multicontainer.md: supersedes
  the no-compose stance of ADR 0004 specifically for the workspace-
  sidecar use case. Documents the named-volume invariant and the hard
  rule that MCP topology stays unchanged (do NOT containerize MCP
  servers into compose services).

MCP topology is intentionally unchanged: MCPs remain stdio children of
claude inside the harness service. The sidecar is a workspace artifact,
not a protocol peer. ADR 0004's MCP rationale still holds for the
protocol layer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Updated `pyproject.toml` for better project metadata organization.
- Enhanced `SKILL.md` documentation for the repo-scan skill.
- Refactored `llm/__init__.py` to maintain code clarity and structure.
- Improved prompt building logic in `prompt/builder.py` for better message handling.
- Updated `claude.py` and `openai.py` provider adapters for consistency and error handling.
- Added comprehensive tests for prompt builder and message adaptation functionality.
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 9 potential issues.

Open in Devin Review

Comment on lines +1 to +7
# Handoff

- Failed: 2026-05-03T18:11:18Z
- Branch: `feat/portable-multicontainer-devcontainer`
- Worktree: `/mnt/c/Users/DavidRevenaugh/AI-Workspace/_projects/harness/agent_harness`

Task file is missing or unreadable: `C:UsersDAVIDR~1AppDataLocalTempecc-orch-debug-79266aee7dec4d99805788fc20534735.orchestrationdocstask.md`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Accidentally committed Windows temp debug artifacts to repo root

Six files with mangled Windows temp paths (e.g., C:UsersDAVIDR~1AppDataLocalTempecc-orch-debug-*.orchestrationdocshandoff.md) were accidentally committed. These are orchestration debug artifacts generated on the developer's local machine. They contain local worktree paths (/mnt/c/Users/DavidRevenaugh/AI-Workspace/_projects/harness/agent_harness), violating CONTRIBUTING.md's rule "Don't include sensitive data (API keys, tokens, paths)" and AGENTS.md's "Hardcoded paths rejected in CI" guideline. These files serve no purpose in the repository and should be removed and gitignored.

Prompt for agents
Remove all six accidentally committed files matching the pattern C:UsersDAVIDR~1AppDataLocalTempecc-orch-debug-*.orchestrationdocs*.md from the repository. These are local debug artifacts that were never meant to be tracked. Run git rm on each of them and add a gitignore pattern to prevent recurrence, for example adding a line like C:Users* to .gitignore.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1 to +16
{
"version": "1.0.0",
"aliases": {
"__proto__": {
"sessionPath": "/evil/path",
"createdAt": "2026-05-03T17:53:45.660Z",
"title": "Prototype Pollution Attempt"
},
"normal": {
"sessionPath": "/normal/path",
"createdAt": "2026-05-03T17:53:45.660Z",
"title": "Normal Alias"
}
},
"metadata": { "totalCount": 2, "lastUpdated": "2026-05-03T17:53:45.660Z" }
} No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Accidentally committed test fixture under undefined/ directory

The file undefined/.claude/session-aliases.json is a test fixture (it contains a __proto__ prototype pollution test entry) that was accidentally committed to the repo root. The undefined directory name strongly suggests a bug in test code where an undefined variable was used as a path component, causing the fixture to be written to a literal undefined/ directory instead of a temp directory. This file should not be tracked in the repository.

Prompt for agents
Remove undefined/.claude/session-aliases.json from the repository (git rm -r undefined/). This is a test fixture that was accidentally written to a literal undefined/ directory, likely because a variable evaluating to undefined was used as a path component in a test. Investigate the test that generates session-aliases.json fixtures (likely in tests/lib/session-manager.test.js or similar) to find and fix the undefined path variable so it does not leak files outside the temp directory. Also add undefined/ to .gitignore as a safety net.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +14 to +40
AGENT_HARNESS_HOST=/c/Users/DavidRevenaugh/AI-Workspace/_projects/harness/agent_harness

# Path to the project you want to work on inside the container.
# It will be mounted at /workspaces/${HARNESS_TARGET_NAME}.
HARNESS_TARGET_HOST=/c/Users/DavidRevenaugh/AI-Workspace/ai-top-utility
HARNESS_TARGET_NAME=ai-top-utility

# Path to prompt-nexus on the host. Mounted into the prompt-nexus sidecar
# at /app, and into the harness service at /workspaces/prompt-nexus so you
# can edit it from either container.
PROMPT_NEXUS_HOST=/c/Users/DavidRevenaugh/AI-Workspace/_projects/prompt_hub/prompt-nexus

# ----------------------------------------------------------------------------
# Unified XDG config dir (host) — same shape on every OS
# ----------------------------------------------------------------------------
# We bind-mount the whole ~/.config so gh, claude, op, etc. persist their
# state across container rebuilds AND match what you use on the host.
#
# Windows is *aligned* with mac/linux rather than catering to %AppData%. See
# docs/RUNBOOKS/portable-devcontainer.md "Windows host alignment" for the
# one-time `setx` + `robocopy` migration that moves an existing gh CLI auth
# from %AppData%\GitHub CLI to %USERPROFILE%\.config\gh.
#
# Linux / macOS: ${HOME}/.config
# WSL on Windows: /c/Users/<user>/.config
# Docker Desktop (Win): /c/Users/<user>/.config
HOST_CONFIG_DIR=/c/Users/DavidRevenaugh/.config
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 .env.example contains hardcoded personal paths instead of generic placeholders

.devcontainer/.env.example contains real personal filesystem paths like /c/Users/DavidRevenaugh/AI-Workspace/_projects/harness/agent_harness and /c/Users/DavidRevenaugh/.config instead of generic placeholders. CONTRIBUTING.md explicitly states "Don't include sensitive data (API keys, tokens, paths)" and AGENTS.md lists "Hardcoded paths rejected in CI" as a common pitfall. Example files should use placeholders like /path/to/your/agent_harness so users know they need to substitute their own values.

Suggested change
AGENT_HARNESS_HOST=/c/Users/DavidRevenaugh/AI-Workspace/_projects/harness/agent_harness
# Path to the project you want to work on inside the container.
# It will be mounted at /workspaces/${HARNESS_TARGET_NAME}.
HARNESS_TARGET_HOST=/c/Users/DavidRevenaugh/AI-Workspace/ai-top-utility
HARNESS_TARGET_NAME=ai-top-utility
# Path to prompt-nexus on the host. Mounted into the prompt-nexus sidecar
# at /app, and into the harness service at /workspaces/prompt-nexus so you
# can edit it from either container.
PROMPT_NEXUS_HOST=/c/Users/DavidRevenaugh/AI-Workspace/_projects/prompt_hub/prompt-nexus
# ----------------------------------------------------------------------------
# Unified XDG config dir (host) — same shape on every OS
# ----------------------------------------------------------------------------
# We bind-mount the whole ~/.config so gh, claude, op, etc. persist their
# state across container rebuilds AND match what you use on the host.
#
# Windows is *aligned* with mac/linux rather than catering to %AppData%. See
# docs/RUNBOOKS/portable-devcontainer.md "Windows host alignment" for the
# one-time `setx` + `robocopy` migration that moves an existing gh CLI auth
# from %AppData%\GitHub CLI to %USERPROFILE%\.config\gh.
#
# Linux / macOS: ${HOME}/.config
# WSL on Windows: /c/Users/<user>/.config
# Docker Desktop (Win): /c/Users/<user>/.config
HOST_CONFIG_DIR=/c/Users/DavidRevenaugh/.config
AGENT_HARNESS_HOST=/path/to/your/agent_harness
# Path to the project you want to work on inside the container.
# It will be mounted at /workspaces/${HARNESS_TARGET_NAME}.
HARNESS_TARGET_HOST=/path/to/your/target-project
HARNESS_TARGET_NAME=your-target-project
# Path to prompt-nexus on the host. Mounted into the prompt-nexus sidecar
# at /app, and into the harness service at /workspaces/prompt-nexus so you
# can edit it from either container.
PROMPT_NEXUS_HOST=/path/to/your/prompt-nexus
# ----------------------------------------------------------------------------
# Unified XDG config dir (host) — same shape on every OS
# ----------------------------------------------------------------------------
# We bind-mount the whole ~/.config so gh, claude, op, etc. persist their
# state across container rebuilds AND match what you use on the host.
#
# Windows is *aligned* with mac/linux rather than catering to %AppData%. See
# docs/RUNBOOKS/portable-devcontainer.md "Windows host alignment" for the
# one-time `setx` + `robocopy` migration that moves an existing gh CLI auth
# from %AppData%\GitHub CLI to %USERPROFILE%\.config\gh.
#
# Linux / macOS: ${HOME}/.config
# WSL on Windows: /c/Users/<user>/.config
# Docker Desktop (Win): /c/Users/<user>/.config
HOST_CONFIG_DIR=/path/to/your/.config
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +137 to +139
} catch {
// git config not set at all — equivalent to default (true). Flag it.
failures.push('commit.gpgsign not set to false (Dockerfile system config drifted?)');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Wrong comment and false-positive validation for unset commit.gpgsign

In validateDevcontainerEnvironment(), when git config --get commit.gpgsign throws (key not found at any level), the catch block comment on line 138 says "equivalent to default (true)" and pushes a failure. However, git's actual default for commit.gpgsign is false — when the key is completely absent, git does not sign commits. This means the validator generates a false-positive failure message ('commit.gpgsign not set to false') in environments where the system-level config is absent but signing is already disabled by git's own default. The misleading comment could also confuse future maintainers.

Suggested change
} catch {
// git config not set at all — equivalent to default (true). Flag it.
failures.push('commit.gpgsign not set to false (Dockerfile system config drifted?)');
} catch {
// git config key not found at any level — git defaults to false (no signing),
// which is fine. Only flag if we positively need to verify the Dockerfile ran.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +142 to +145
const xdg = process.env.XDG_CONFIG_HOME;
if (xdg !== '/home/node/.config') {
failures.push(`XDG_CONFIG_HOME is "${xdg || '<unset>'}" — expected "/home/node/.config" (profile drop did not run?)`);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 Profile drop only applies to interactive login shells, not all shell invocations

The /etc/profile.d/ecc-env.sh drop (.devcontainer/Dockerfile:59-61) unsets GITHUB_TOKEN and GH_TOKEN and exports XDG_CONFIG_HOME. However, /etc/profile.d/ scripts are only sourced by login shells (bash -l or bash --login). Non-login shells (e.g., docker compose exec harness bash without -l, or programmatic child_process.exec calls) won't source this file. The validateDevcontainerEnvironment() in scripts/hooks/session-start.js:142-145 checks XDG_CONFIG_HOME expecting it to be /home/node/.config, but the hook runs via node (not a login shell), so it will typically find XDG_CONFIG_HOME unset unless something else sets it. This means the validator will almost always report a false positive for XDG_CONFIG_HOME unless the compose environment: block or env_file: also sets it. The compose file at .devcontainer/docker-compose.yml:34-35 includes env_file: .env but the .env.example does not include XDG_CONFIG_HOME. The troubleshooting docs at docs/RUNBOOKS/portable-devcontainer.md:130-145 partially address this for gh auth but not for XDG_CONFIG_HOME.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread docs/zh-CN/README.md
Comment on lines +1136 to +1137
| 命令 | PASS: 68 个 | PASS: 31 个 | **Claude Code 领先** |
| 技能 | PASS: 190 项 | PASS: 37 项 | **Claude Code 领先** |
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: zh-CN README command/skill count updates are internally consistent

The PR changes the command count from 69→68 and skills count from 189→190 in docs/zh-CN/README.md at two locations (lines 1136-1137 and 1244-1245). The root AGENTS.md declares "190 skills, 68 commands" in its header, and docs/zh-CN/AGENTS.md also says "190 项技能、68 条命令". So these count changes are a sync correction to make the zh-CN README consistent with AGENTS.md. This is correct and not a bug.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +34 to +35
env_file:
- .env
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Compose env_file path is relative to compose file, not project root

In .devcontainer/docker-compose.yml:34-35, env_file: .env is specified. Docker Compose resolves env_file paths relative to the compose file's directory (.devcontainer/), so this correctly references .devcontainer/.env. This is not a bug, but worth noting because it's a common source of confusion — if someone moves the compose file, the env_file reference would break.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +29 to +31
function sleepSync(ms) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Test uses Atomics.wait with SharedArrayBuffer for synchronous sleep

The sleepSync function in tests/scripts/orchestrate-codex-worker.test.js:29-31 uses Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms) for a synchronous sleep. This works in Node.js but requires --enable-source-maps or similar flags in some configurations, and SharedArrayBuffer requires specific security headers in browser contexts (though this is Node.js only). The approach is valid for test code — it's a well-known pattern for blocking the main thread in Node.js without busy-waiting. The retry logic in removeTempRoot (lines 33-47) is a reasonable fix for Windows file-locking issues during temp directory cleanup.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- ${AGENT_HARNESS_HOST}:/workspaces/agent_harness:cached
- ${HARNESS_TARGET_HOST}:/workspaces/${HARNESS_TARGET_NAME}:cached
- ${PROMPT_NEXUS_HOST}:/workspaces/prompt-nexus:cached
- ${HOST_CONFIG_DIR}:/home/node/.config
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 HOST_CONFIG_DIR bind-mount gives container full write access to host's ~/.config

The compose volume mount ${HOST_CONFIG_DIR}:/home/node/.config (.devcontainer/docker-compose.yml:29) gives the container read-write access to the entire ~/.config directory on the host. This is intentional (for gh auth persistence), but it means any process in the container can read/modify all XDG config data on the host — not just gh/. A misconfigured or malicious MCP server running inside the container could access other tool configurations. The ADR acknowledges this is for persistence but doesn't discuss the blast radius. Consider using :ro (read-only) or mounting only specific subdirectories like gh/ if full write access isn't needed for all tools.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 58e6f1a7a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,7 @@
# Handoff
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove invalid Windows-path debug files from repository

This commit adds multiple generated files whose names start with C:Users..., which include : characters that cannot exist in Windows filenames. On Windows checkouts, Git cannot materialize these paths, so the commit becomes unusable for contributors/CI on a supported platform. These should be removed from version control (and ignored) so cross-platform clones/checkouts keep working.

Useful? React with 👍 / 👎.

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.

1 participant