Skip to content

docs: README — clide-owned venv, pip install pattern, git safe.directory, codex in diagram - #56

Merged
itscooleric merged 2 commits into
fix/pyenv-writable-git-safe-dirfrom
copilot/sub-pr-55
Mar 8, 2026
Merged

itscooleric merged 2 commits into
fix/pyenv-writable-git-safe-dirfrom
copilot/sub-pr-55

Conversation

Copilot AI commented Mar 7, 2026 •

Copy link
Copy Markdown
Contributor

README was not reflecting two recent Dockerfile changes (clide-owned /opt/pyenv, safe.directory = *) and was missing the codex service from the architecture diagram.

Changes

  • Architecture diagram — added missing codex service node
  • Python dev tooling — clarified /opt/pyenv is a plain python3 -m venv (not the pyenv version manager); noted venv is clide-owned so pip install works without sudo or a rebuild; updated example to pip install -r /workspace/<repo>/requirements.txt; added firewall note (pypi.org,files.pythonhosted.org needed in CLIDE_ALLOWED_HOSTS)
  • Git workspace repos (new section) — documents safe.directory = * pre-configured at build time, why it's needed (host-UID-owned mounts trigger dubious ownership), and a security callout recommending /workspace scope if the container is shared

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…fe.directory section

Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

Copilot AI commented Mar 7, 2026

Copy link
Copy Markdown
Contributor Author

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cli.github.com
    • Triggering command: /usr/bin/curl curl -fsSL REDACTED -o /etc/apt/keyrings/githubcli-archive-keyring.gpg /var/lib/dpkg/reassemble.deb la/SSL.com_Root_Certification_Authority_ECC.crt e deb (dns block)
    • Triggering command: /usr/bin/curl curl -s --max-time 5 REDACTED -o /dev/null -w %{http_code} (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title [WIP] Fix writable pyenv for agent with git safe.directory pre-configured docs: README — clide-owned venv, pip install pattern, git safe.directory, codex in diagram Mar 7, 2026
@itscooleric
itscooleric marked this pull request as ready for review March 8, 2026 01:38
@itscooleric
itscooleric merged commit 3365058 into fix/pyenv-writable-git-safe-dir Mar 8, 2026
itscooleric added a commit that referenced this pull request Mar 8, 2026
* fix: make pyenv writable by clide + pre-configure git safe.directory

Two quality-of-life improvements for agent dev sessions:

**1. Writable Python venv (/opt/pyenv)**
Transfer /opt/pyenv ownership to the clide user so the agent can
`pip install -r /workspace/<repo>/requirements.txt` on demand without a
container rebuild. Previously the venv was root-owned, causing permission
errors when installing workspace project dependencies (e.g. clem's PyYAML,
Flask, pydantic). pytest + ruff remain pre-installed as a baseline; all other
deps are installed per-session as needed.

**2. git safe.directory = * (eliminate per-session config noise)**
Volume-mounted workspace repos are often owned by the host UID, which
differs from clide (UID 1000). Git refuses to operate on these repos unless
`safe.directory` is configured, causing wasted tokens and boilerplate at the
start of every session. Setting `safe.directory = *` once at build time in
the clide user's gitconfig eliminates this entirely. This is appropriate for
a single-user dev sandbox.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: README — clide-owned venv, pip install pattern, git safe.directory, codex in diagram (#56)

* Initial plan

* docs: update README for clide-owned venv, pip install pattern, git safe.directory section

Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>
@itscooleric
itscooleric deleted the copilot/sub-pr-55 branch March 8, 2026 22:59
itscooleric added a commit that referenced this pull request Apr 23, 2026
* Initial plan

* feat: add PR checks GitHub Actions workflow

Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

* fix: pin Node.js to 22.x and ttyd to 1.7.7, fix hadolint issues

- setup_lts.x → setup_22.x (prevents surprise LTS major jumps)
- ttyd /latest/ → pinned 1.7.7 (current stable)
- Replace wget with curl throughout (DL4001)
- Add --no-install-recommends to all apt-get install (DL3015)
- Add SHELL pipefail before pipe commands (DL4006)
- Add .hadolint.yaml ignoring DL3008/DL3016 (npm/apt tools intentionally unpinned)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: resolve CI failures in hadolint, compose-validate, markdown-lint

- Dockerfile: quote $(uname -m) via ARCH variable to fix SC2046
- Workflow: add config: .hadolint.yaml so hadolint picks up ignore rules
- Workflow: cp .env.example .env before compose-validate to provide env file
- Add .markdownlint.yaml: disable MD013/022/031/032 (style-only rules)
- DEPLOY.md: remove stray closing fence with no matching open

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: resolve remaining CI failures (hadolint ignore, MD034, MD040)

- Workflow: add ignore: DL3008 DL3016 to hadolint step (config file
  path wasn't being resolved correctly inside the action container)
- README.md: add language specifier to env code fences (MD040)
- .markdownlint.yaml: disable MD034 (bare URLs in technical docs)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use inline hadolint ignore directives before each RUN instruction

The hadolint-action ignore input and .hadolint.yaml config file were not
being applied. Inline directives are the most reliable approach.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add claude-entrypoint.sh to skip first-run prompts, enforce CLAUDE_CODE_SIMPLE=1

Adds a dedicated entrypoint for the claude service that pre-seeds
~/.claude.json (onboarding flags + workspace trust) so the container
starts directly into the CLI without interactive setup prompts.
CLAUDE_CODE_SIMPLE=1 is now set consistently via docker-compose.yml
(default), Makefile, and the clide wrapper script. README and DEPLOY
docs updated with startup behaviour notes and a reset troubleshooting
step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove unused CLAUDE_CONFIG var (SC2034), extend entrypoint to shell service

- Drop the unused `CLAUDE_CONFIG` bash variable from claude-entrypoint.sh
  to fix shellcheck SC2034 warning
- Change `exec claude "$@"` to `exec "${@:-claude}"` so the entrypoint
  is reusable: no args → runs claude (existing behaviour); args → runs
  those instead (e.g. /bin/bash for the shell service)
- Wire the shell service in docker-compose.yml to use claude-entrypoint.sh
  with /bin/bash as command, so claude config is pre-seeded before the
  interactive shell starts and `claude` works without prompts from make shell
- Update README to reflect that shell also benefits from pre-seeding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* [#16] Update .env.example and README with git identity env vars (#17)

* docs: add git identity vars to .env.example and README

Adds GIT_AUTHOR_* / GIT_COMMITTER_* placeholders to .env.example so
git authorship persists correctly across container runs without needing
git config --global on each session.

Also updates README setup step to reference cp .env.example .env
as the starting point rather than manual inline snippets.

Closes #16

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add OAuth token support for Claude Code, consolidate auth + identity docs

- claude-entrypoint.sh: detect CLAUDE_CODE_OAUTH_TOKEN, skip primaryApiKey
  injection when OAuth is set, log which auth method is active, unset
  ANTHROPIC_API_KEY from env when OAuth takes priority
- docker-compose.yml: pass CLAUDE_CODE_OAUTH_TOKEN through to all services
- .env.example: replace single API key comment with structured OAuth/API key
  section; restore git identity vars block
- README.md: update prerequisites and CLIs table for dual auth; add
  Claude Code authentication section; consolidate setup step 1 to show
  OAuth, API key, and git identity vars together

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): Claude auth + tmux multi-pane for web terminal (#22)

* fix(web): pre-seed Claude auth in web terminal + add tmux multi-pane support

- entrypoint.sh now calls claude-entrypoint.sh at container start so
  CLAUDE_CODE_OAUTH_TOKEN / ANTHROPIC_API_KEY from .env are wired into
  .claude.json before any ttyd bash session runs `claude` (fixes #21)
- Mirrors the ANTHROPIC_API_KEY env cleanup from claude-entrypoint.sh
  so web terminal env matches `make shell` exactly
- Install tmux in Dockerfile; ship a .tmux.conf with mouse support,
  sane splits (| and -), 256-colour, and status bar (closes #18)
- Web terminal (ttyd) now launches into `tmux new-session -A -s main`
  so browser refreshes re-attach to the existing session
- Shell service gets opt-in tmux via CLIDE_TMUX=1 in .env, leaving
  existing single-pane behaviour unchanged by default
- Expose CLIDE_TMUX in docker-compose base env block
- README: document tmux shortcuts and CLIDE_TMUX opt-in

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(entrypoint): suppress CLIDE_TMUX during claude-entrypoint pre-seed call, add sync comment

- Pass CLIDE_TMUX= (blank) when calling claude-entrypoint.sh from entrypoint.sh
  so the tmux opt-in branch is skipped — the web terminal manages its own tmux
  session via ttyd and doesn't need a transient session spun up during pre-seeding
- Add "keep in sync" comment on the duplicated env cleanup block to make it obvious
  it mirrors the logic in claude-entrypoint.sh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve CI failures (shellcheck SC1007, markdownlint MD036)

- entrypoint.sh: quote empty string in inline env assignment (CLIDE_TMUX='')
- README.md: replace bold paragraphs with #### headings for Option 1/2
  to satisfy MD036 (no-emphasis-as-heading)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add .dockerignore to exclude secrets and non-essential files from build context (#24)

* Security: run container as non-root user (#23)

* Initial plan

* Security: run container as non-root user (clide, UID 1000)

Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

* fix: harden HOME handling in claude entrypoint (#26)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>
Co-authored-by: eric is cool <e@ericiscool.net>

* Add configurable iptables egress firewall for container network isolation (#25)

* Initial plan

* Add configurable iptables egress firewall (CLIDE_FIREWALL / CLIDE_ALLOWED_HOSTS)

Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

* feat: add Python 3 dev tooling (pytest + ruff) to container (#32)

Installs python3/python3-venv via apt and creates a shared venv at
/opt/pyenv with pytest and ruff pre-installed on PATH. Lets Claude
Code run Python test suites and lint/format code without any manual
setup inside the container.

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: ttyd auth default-on, gh credential helper, Dockerfile HEALTHCHECK (#33)

* fix: ttyd auth default-on, gh credential helper, Dockerfile HEALTHCHECK

- (#4) Flip ttyd auth logic: credentials now required by default; container
  exits with a clear error if neither creds nor TTYD_NO_AUTH=true are set.
  Added TTYD_NO_AUTH=true as the explicit opt-out for unauthenticated access.
- (#14) Add `gh auth setup-git` to entrypoint so git push/fetch works
  seamlessly via GH_TOKEN without embedding tokens in remote URLs.
- (#10) Add HEALTHCHECK to Dockerfile — curls ttyd every 30s with a 10s
  startup grace period and 3 retries before marking the container unhealthy.
- Updated .env.example to document new ttyd auth defaults and TTYD_NO_AUTH.

Closes #4, #10, #14

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address Copilot review feedback on PR #33

- gh auth setup-git is now best-effort: skips gracefully if GH_TOKEN is
  absent or gh is unauthenticated, so the web terminal can still start
  without GitHub credentials.
- Fix TTYD auth precedence: credentials now take priority over
  TTYD_NO_AUTH=true; setting both is treated as a hard config error
  rather than silently disabling auth.
- HEALTHCHECK now respects TTYD_BASE_PATH so it doesn't produce false
  unhealthy status when a non-root base path is configured.
- HEALTHCHECK validates TTYD_PORT is numeric before use to prevent
  shell injection via environment variable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: gosu privilege drop for firewall + compose hardening (#34)

* fix: gosu privilege drop for firewall, compose hardening

Fixes the silent firewall no-op (#30) caused by iptables requiring
geteuid()==0 even with CAP_NET_ADMIN when the container runs as the
non-root clide user.

- (#30) Install gosu; set Dockerfile back to USER root so entrypoints
  start as root. All entrypoints (firewall.sh, claude-entrypoint.sh,
  entrypoint.sh) now apply any root-only setup first (iptables, config
  writes) then exec gosu clide to drop privileges before the workload.
  The firewall is now always effective when NET_ADMIN is present.
- (#30) Hardcode HOME_DIR=/home/clide in claude-entrypoint.sh to avoid
  resolving to /root when running as root; chown .claude.json to clide
  after writing.
- (#30) gosu clide gh auth setup-git in entrypoint.sh so gh config
  writes to clide's home, not root's.
- (#6) Add cap_drop: ALL, security_opt: no-new-privileges:true, and
  resource guardrails (mem_limit: 4g, cpus: 4.0, pids_limit: 512) to
  the docker-compose.yml base anchor. cap_add: NET_ADMIN is preserved
  for the firewall.

Closes #30, #6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: suppress DL3002 for intentional USER root before gosu drop

The last USER is root by design — entrypoints apply iptables rules as
root then exec gosu clide to drop privileges before the workload starts.
Suppress hadolint DL3002 with a pragma rather than papering over the
intentional pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: run PR checks on PRs targeting dev (#38)

* fix: ttyd auth default-on, gh credential helper, Dockerfile HEALTHCHECK (#33)

* fix: ttyd auth default-on, gh credential helper, Dockerfile HEALTHCHECK

- (#4) Flip ttyd auth logic: credentials now required by default; container
  exits with a clear error if neither creds nor TTYD_NO_AUTH=true are set.
  Added TTYD_NO_AUTH=true as the explicit opt-out for unauthenticated access.
- (#14) Add `gh auth setup-git` to entrypoint so git push/fetch works
  seamlessly via GH_TOKEN without embedding tokens in remote URLs.
- (#10) Add HEALTHCHECK to Dockerfile — curls ttyd every 30s with a 10s
  startup grace period and 3 retries before marking the container unhealthy.
- Updated .env.example to document new ttyd auth defaults and TTYD_NO_AUTH.

Closes #4, #10, #14

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address Copilot review feedback on PR #33

- gh auth setup-git is now best-effort: skips gracefully if GH_TOKEN is
  absent or gh is unauthenticated, so the web terminal can still start
  without GitHub credentials.
- Fix TTYD auth precedence: credentials now take priority over
  TTYD_NO_AUTH=true; setting both is treated as a hard config error
  rather than silently disabling auth.
- HEALTHCHECK now respects TTYD_BASE_PATH so it doesn't produce false
  unhealthy status when a non-root base path is configured.
- HEALTHCHECK validates TTYD_PORT is numeric before use to prevent
  shell injection via environment variable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: gosu privilege drop for firewall + compose hardening (#34)

* fix: gosu privilege drop for firewall, compose hardening

Fixes the silent firewall no-op (#30) caused by iptables requiring
geteuid()==0 even with CAP_NET_ADMIN when the container runs as the
non-root clide user.

- (#30) Install gosu; set Dockerfile back to USER root so entrypoints
  start as root. All entrypoints (firewall.sh, claude-entrypoint.sh,
  entrypoint.sh) now apply any root-only setup first (iptables, config
  writes) then exec gosu clide to drop privileges before the workload.
  The firewall is now always effective when NET_ADMIN is present.
- (#30) Hardcode HOME_DIR=/home/clide in claude-entrypoint.sh to avoid
  resolving to /root when running as root; chown .claude.json to clide
  after writing.
- (#30) gosu clide gh auth setup-git in entrypoint.sh so gh config
  writes to clide's home, not root's.
- (#6) Add cap_drop: ALL, security_opt: no-new-privileges:true, and
  resource guardrails (mem_limit: 4g, cpus: 4.0, pids_limit: 512) to
  the docker-compose.yml base anchor. cap_add: NET_ADMIN is preserved
  for the firewall.

Closes #30, #6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: suppress DL3002 for intentional USER root before gosu drop

The last USER is root by design — entrypoints apply iptables rules as
root then exec gosu clide to drop privileges before the workload starts.
Suppress hadolint DL3002 with a pragma rather than papering over the
intentional pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: run PR checks on PRs targeting dev branch

Adds dev to the pull_request branch filter so CI runs on feature
branches merging into dev, not just main.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add Codex CLI (OpenAI) support (#35)

* Initial plan

* feat: add Codex CLI (OpenAI) support

Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

* fix: forward args to codex wrapper command (#36)

* fix: suppress DL3059 for consecutive npm RUN instructions

hadolint flags consecutive RUN instructions; suppressed with a pragma
since consolidating the npm installs would hurt layer caching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: trigger checks after rebase

* chore: run PR checks on PRs targeting dev (#38)

* fix: ttyd auth default-on, gh credential helper, Dockerfile HEALTHCHECK (#33)

* fix: ttyd auth default-on, gh credential helper, Dockerfile HEALTHCHECK

- (#4) Flip ttyd auth logic: credentials now required by default; container
  exits with a clear error if neither creds nor TTYD_NO_AUTH=true are set.
  Added TTYD_NO_AUTH=true as the explicit opt-out for unauthenticated access.
- (#14) Add `gh auth setup-git` to entrypoint so git push/fetch works
  seamlessly via GH_TOKEN without embedding tokens in remote URLs.
- (#10) Add HEALTHCHECK to Dockerfile — curls ttyd every 30s with a 10s
  startup grace period and 3 retries before marking the container unhealthy.
- Updated .env.example to document new ttyd auth defaults and TTYD_NO_AUTH.

Closes #4, #10, #14

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address Copilot review feedback on PR #33

- gh auth setup-git is now best-effort: skips gracefully if GH_TOKEN is
  absent or gh is unauthenticated, so the web terminal can still start
  without GitHub credentials.
- Fix TTYD auth precedence: credentials now take priority over
  TTYD_NO_AUTH=true; setting both is treated as a hard config error
  rather than silently disabling auth.
- HEALTHCHECK now respects TTYD_BASE_PATH so it doesn't produce false
  unhealthy status when a non-root base path is configured.
- HEALTHCHECK validates TTYD_PORT is numeric before use to prevent
  shell injection via environment variable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: gosu privilege drop for firewall + compose hardening (#34)

* fix: gosu privilege drop for firewall, compose hardening

Fixes the silent firewall no-op (#30) caused by iptables requiring
geteuid()==0 even with CAP_NET_ADMIN when the container runs as the
non-root clide user.

- (#30) Install gosu; set Dockerfile back to USER root so entrypoints
  start as root. All entrypoints (firewall.sh, claude-entrypoint.sh,
  entrypoint.sh) now apply any root-only setup first (iptables, config
  writes) then exec gosu clide to drop privileges before the workload.
  The firewall is now always effective when NET_ADMIN is present.
- (#30) Hardcode HOME_DIR=/home/clide in claude-entrypoint.sh to avoid
  resolving to /root when running as root; chown .claude.json to clide
  after writing.
- (#30) gosu clide gh auth setup-git in entrypoint.sh so gh config
  writes to clide's home, not root's.
- (#6) Add cap_drop: ALL, security_opt: no-new-privileges:true, and
  resource guardrails (mem_limit: 4g, cpus: 4.0, pids_limit: 512) to
  the docker-compose.yml base anchor. cap_add: NET_ADMIN is preserved
  for the firewall.

Closes #30, #6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: suppress DL3002 for intentional USER root before gosu drop

The last USER is root by design — entrypoints apply iptables rules as
root then exec gosu clide to drop privileges before the workload starts.
Suppress hadolint DL3002 with a pragma rather than papering over the
intentional pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: run PR checks on PRs targeting dev branch

Adds dev to the pull_request branch filter so CI runs on feature
branches merging into dev, not just main.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: re-trigger checks after workflow update

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>
Co-authored-by: eric is cool <e@ericiscool.net>
Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: architecture diagram, threat model, runbook, compatibility matrix (#37)

* docs: architecture diagram, threat model, runbook, compatibility matrix

- (#12) Add Mermaid architecture diagram to README showing host/container
  trust boundary, services, and allowlisted internet endpoints.
- (#8)  Add SECURITY.md — trust boundaries, attack surface breakdown,
  threat scenario table, and deployment hardening recommendations.
- (#11) Add RUNBOOK.md — health checks, start/stop, logs, rebuild
  procedures, credential rotation, firewall and web terminal
  troubleshooting, Docker health state reference.
- (#9)  Add compatibility matrix to README covering host OS, Docker
  version, CPU architecture, browser support, and firewall requirements.
- Link SECURITY.md and RUNBOOK.md from README "Additional docs" table.

Closes #8, #9, #11, #12

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: re-trigger checks after workflow update

* fix: resolve markdown-lint failures in docs PR

- Rename duplicate 'Architecture' heading to 'CPU architecture' (MD024)
- Rename duplicate 'Egress firewall' heading to 'Egress firewall support' (MD024)
- Add 'text' language to ASCII art fenced block in SECURITY.md (MD040)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: change clide UID from 1000 to 1001 to avoid conflict with Ubuntu 24.04 built-in ubuntu user

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove redundant mkdir/chown for /home/clide in entrypoint

useradd -m in the Dockerfile already creates /home/clide with correct
ownership. The chown was also failing at runtime due to cap_drop: ALL
removing the CHOWN capability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: run claude config node script as clide user via gosu

cap_drop: ALL removes CAP_DAC_OVERRIDE, meaning root inside the container
is subject to normal file permission checks and cannot write to /home/clide.
Running the node script via gosu clide ensures it writes the config as the
correct user.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add SETUID and SETGID capabilities required by gosu

cap_drop: ALL removes CAP_SETUID and CAP_SETGID, which gosu needs to
switch from root to the clide user in entrypoint scripts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add missing GitHub hosts to firewall allowlist

git clone/fetch/pull serve pack objects via objects.githubusercontent.com,
raw file access goes through raw.githubusercontent.com, and gh release
uploads use uploads.github.com — all previously blocked by the egress filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add egress reject audit logging (LOG before REJECT in firewall.sh)

Closes #40. Adds rate-limited LOG target (10/min, burst 5) before REJECT rules for both IPv4 and IPv6. Logs appear under CLIDE-REJECT: prefix in dmesg/kern.log.

* fix: writable pyenv for agent + git safe.directory pre-configured (#55)

* fix: make pyenv writable by clide + pre-configure git safe.directory

Two quality-of-life improvements for agent dev sessions:

**1. Writable Python venv (/opt/pyenv)**
Transfer /opt/pyenv ownership to the clide user so the agent can
`pip install -r /workspace/<repo>/requirements.txt` on demand without a
container rebuild. Previously the venv was root-owned, causing permission
errors when installing workspace project dependencies (e.g. clem's PyYAML,
Flask, pydantic). pytest + ruff remain pre-installed as a baseline; all other
deps are installed per-session as needed.

**2. git safe.directory = * (eliminate per-session config noise)**
Volume-mounted workspace repos are often owned by the host UID, which
differs from clide (UID 1000). Git refuses to operate on these repos unless
`safe.directory` is configured, causing wasted tokens and boilerplate at the
start of every session. Setting `safe.directory = *` once at build time in
the clide user's gitconfig eliminates this entirely. This is appropriate for
a single-user dev sandbox.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: README — clide-owned venv, pip install pattern, git safe.directory, codex in diagram (#56)

* Initial plan

* docs: update README for clide-owned venv, pip install pattern, git safe.directory section

Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

* feat: add persistent session memory across container restarts (#57)

* feat: add persistent session memory across container restarts

- Add named volume (claude-data) for ~/.claude so auto memory, settings,
  and project learnings survive container restarts
- Seed CLAUDE.md from a template on first run if none exists in /workspace
- Template search order: project .claude/CLAUDE.md.template → bundled default
- Bundle a sensible default CLAUDE.md.template in the image

https://claude.ai/code/session_012jgpaUaGUgmGyoH9edLbyg

* fix: disable MD060 lint rule and remove trailing blank line in README

The markdownlint-cli2 action upgraded to markdownlint v0.40.0 which
introduced MD060 (table-column-style). Disable it to match existing
compact table style, and fix one MD012 violation (double blank line).

https://claude.ai/code/session_012jgpaUaGUgmGyoH9edLbyg

---------

Co-authored-by: Claude <noreply@anthropic.com>

* docs: replace mermaid diagram with ASCII art in README

Mermaid doesn't render in all contexts (terminal, raw markdown viewers,
email). ASCII box-drawing diagram shows the same architecture and works
everywhere.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add CLI-style ASCII banner to README

Block-letter logo with architecture summary — shows the trust model,
egress allowlist, and key selling points at a glance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add glab (GitLab CLI) to container (closes #62)

- Dockerfile: install glab v1.47.0 binary (pinned, single deb install)
- docker-compose.yml: pass GITLAB_TOKEN + GITLAB_HOST env vars through
- entrypoint.sh + claude-entrypoint.sh: wire glab config on startup,
  best-effort — missing token prints a notice but never blocks startup
- .env.example: document GITLAB_TOKEN + GITLAB_HOST with group token guidance
- README: add glab to Included CLIs table

Group access token is the recommended auth method — scoped to one group,
not tied to a personal account, survives credential rotation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add language tag to ASCII banner code block (MD040)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: replace named volume with workspace-local .clide/ directory

Move Claude session storage from the `claude-data` named Docker volume
(`/home/clide/.claude`) into `/workspace/.clide/` on the bind-mounted
project directory. This eliminates the root-ownership permission issues
that Docker named volumes cause and keeps all clide state in one place.

The entrypoint now:
- Creates /workspace/.clide/ with correct clide ownership
- Symlinks ~/.claude → /workspace/.clide/
- Migrates existing data from old named-volume mounts automatically

https://claude.ai/code/session_01CvrggoLEZAcjJaoQFggHs9

* fix: prevent chown failure from crashing entrypoint on bind mounts

The container drops all capabilities and only re-adds a few. Without the
CHOWN/FOWNER capabilities, `chown clide:clide /workspace/.clide` fails
with "Operation not permitted", and `set -euo pipefail` causes the
entire entrypoint to abort.

- Add CHOWN and FOWNER to cap_add in docker-compose.yml
- Make all chown calls in claude-entrypoint.sh non-fatal with chmod 777
  fallback, so the script degrades gracefully on filesystems that don't
  support ownership changes (NFS, FUSE, rootless Docker)

https://claude.ai/code/session_01JrufWPKGMLtHPmCks8kj9A

* fix: add DAC_OVERRIDE cap and configurable UID/GID for bind mount permissions

cap_drop: ALL removes DAC_OVERRIDE, preventing root from creating
/workspace/.clide on bind mounts owned by the host user. Adding it back
lets the entrypoint set up persistent storage regardless of host UID.

Also makes clide's UID/GID configurable via CLIDE_UID/CLIDE_GID build
args (default 1000) so the container user matches the host user for
normal file operations. Closes #53.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add -l flag to useradd to satisfy hadolint DL3046

Prevents excessively large images when high UIDs are used.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pin all dependency versions in Dockerfile

Pin npm and pip packages to specific versions for reproducible builds:
- @anthropic-ai/claude-code@2.1.71
- @openai/codex@0.112.0
- pytest==9.0.2
- ruff==0.15.5

All versions use build ARGs (npm) or inline pins (pip) so they're easy
to bump. Removes the DL3016 hadolint ignore now that npm packages are
pinned.

Closes #7

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: create ~/.claude symlink as clide user instead of root

The entrypoint runs as root but /home/clide is owned by clide. Without
DAC_OVERRIDE (or on older cached images), root can't create symlinks
there. Use gosu clide for all symlink operations in clide's home dir.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: document all CLIDE_* environment variables in README

Add a comprehensive reference table covering CLIDE_UID, CLIDE_GID,
CLIDE_FIREWALL, CLIDE_ALLOWED_HOSTS, CLIDE_TMUX, and CLIDE_FIREWALL_DONE
with defaults, where each is set, and descriptions.

Closes #7

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: warn about clide-web-1 restart destroying in-memory state

Add a prominent warning to the RUNBOOK explaining that /workspace
persists (bind mount) but tmux sessions, running processes, shell
history, and temp files are lost on container restart.

Closes #11

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: document Docker socket non-exposure and CI guard

Add a dedicated section to SECURITY.md explaining that the Docker
socket is intentionally not mounted, why this matters for preventing
container escape, and providing a sample CI guard to enforce this.

Closes #14

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bootstrap: LICENSE + scrub internal refs for public release

- Added MIT LICENSE
- Replaced wubi.sh domain refs with example.com placeholders
- Replaced Bernard references with generic 'production host'

Tracking: forge/eric#12, career#27

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* clide#15: README rewrite — research/data framing

* fix: add language specifier to bare fenced code blocks (MD040)

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
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.

2 participants