Execution Log: pre-trust the workspace before spawning a harness session
Append-only log of progress for the user's visibility. Checked in alongside the spec at
docs/specs/issue-90/.
Phase transitions
| Phase | Entered | Reviewed/approved by | Notes |
|---|---|---|---|
| requirements-definition | 2026-07-25 | Issue #90: claude still asks to trust the workspace even under --dangerously-skip-permissions; the-loop should write the required config before spawning. | |
| design | 2026-07-25 | Three seams: new trust.py (config layout + safe read-merge-write), a prepare_environment() hook on the adapter contract, and dispatcher calls on both spawn paths. | |
| tasks-breakdown | 2026-07-25 | 12-task DAG | |
| implementation | 2026-07-25 | Implemented on claude/github-issue-90-y6uqhg | |
| needs-review | 2026-07-25 | PR #92 opened; rebased onto #91 (issue-89); awaiting human review + the tier-4 named security sign-off | |
| complete |
Progress entries
2026-07-25 — spec drafted
- Phase: requirements → design → tasks
- Did: Established the actual mechanism before designing anything, by reading it off the shipped Claude Code CLI rather than guessing: workspace trust lives in
projects[<path>].hasTrustDialogAcceptedin the user config file (with the lookup walking up from the cwd), onboarding inhasCompletedProjectOnboardingbeside it, and the bypass-permissions disclaimer inskipDangerousModePermissionPromptin the user settings file (current builds migrate the legacy top-levelbypassPermissionsModeAcceptedinto it). That also explains the issue's core observation: none of these is a permission rule, so--dangerously-skip-permissionscannot silence them. Two findings shaped the design:- ancestor entries grant trust, which makes "trust the workspace root once" tempting and wrong — it would silently trust every future checkout under the root, so the design writes the exact spawn directory only, with a dedicated regression test;
CLAUDE_CODE_SANDBOXED=1short-circuits the trust check entirely, i.e. there is a one-line "fix" available — rejected in design §8 because asserting a sandbox that does not exist is a lie to the harness.
- Risk tier: raised to 4. This writes to the operator's own global harness config and can pre-accept a safety disclaimer on their behalf, which is a wider blast radius than the usual CLI change;
security.humanSignOffMinTier: 4therefore applies and the PR briefing requests a named security sign-off. - Next: implement T1–T12.
- Blockers: none.
2026-07-25 — implemented (T1–T12)
- Phase: implementation
- Did:
- T1/T2 — new
cli/the_loop/trust.py:TrustConfig/TrustResult, the_update_jsonread-merge-write (no-write-when-unchanged, temp file +os.replace,0600on creation, per-path lock, refuse-on-unparseable) andClaudeTrustStore(config-dir/config-file/settings-file resolution honouringCLAUDE_CONFIG_DIRand the.config.jsonpreference; exact-directory project keys;trust();accept_bypass_permissions()). - T3 —
HarnessAdapter.prepare_environment(cwd)(base no-op, so cursor-agent needs nothing),ClaudeCodeAdapterimplementation withauto | always | neverbypass gating,build_adapters(..., trust). - T4 —
RoutingConfig.harness_trust, threaded through both CLI entry points andreload(), plusDispatcher._prepare_environmentcalled from_spawn_for(before either runner) and_respawn_tmux. Never raises, never changes the dispatch outcome. - T5 —
workspace.trusted/workspace.trust_failedevent types. - T6/T7 — 30 unit tests + 5 Gherkin integration scenarios. The integration tests assert ordering (the trust key was already on disk at the moment the harness was started), not merely the end state — that is what proves the dialog cannot appear.
- T8/T9/T10 — schema + both
cli-config.yamls,cli/README.md, the observability and automation references, both affected capability docs with history rows, anddecision-037.
- T1/T2 — new
- Evidence:
ruff check+ruff format --checkclean,pyright0 errors,markdownlint0 errors,validate_config.pyall VALID,pytest352 passed (37 of them new). - Next: self/critic review, then open the PR with the briefing.
- Blockers: none.
2026-07-25 — rebased onto main (issue-89 landed in between)
- Phase: needs-review
- Did: Owner asked for a rebase on PR #92.
mainhad gained #91 (issue-89: resume the harness conversation when a dead tmux session is respawned), which rewrote the exact function this work item also touches. Two conflicts:cli/README.md— a heading #91 reworded, adjacent to the newharnessTrustsection. Textual; took both.webhook/dispatcher.py::_respawn_tmux— the substantive one. issue-89 split the respawn into a conversation-resume attempt followed by a fresh-conversation fallback, and_try_resume()starts a harness process of its own. Resolving the conflict "where the lines used to be" would have put the trust write between the two starts, leaving the resume path — the common case — still stalling on the dialog. Moved the_prepare_environmentcall above_try_resumeso it precedes every harness start.
- Test follow-through:
TmuxRunner.spawngained aresumekwarg, so theDeadTmuxdouble needed it; and the respawn now callsspawntwice, which broke an assertion that assumed exactly one call. Rather than relax it, strengthened it: the test now asserts the directory was trusted at every recorded harness start, which is the property that actually matters and would have caught the naive conflict resolution. - Evidence:
ruff+pyright+markdownlintclean, config validation all VALID,pytest367 passed. - Next: awaiting the owner's review and the tier-4 security sign-off.
- Blockers: none.
2026-07-25 — trust scope made configurable, defaulting to the workspace root
- Phase: needs-review
- Did: Owner's call on PR #92 — "this should be configurable. and default should be trust all folders within the workspace root. if claude needs additional permissions per repo, then what we are doing in this PR should still be kept." This reverses the least-privilege default this PR shipped with; recorded as such in decision-037 ("Scope: the owner's call") rather than quietly restated, because the original position was argued in the spec.
- The finding that shaped the implementation: checked the shipped CLI before writing anything, and the two project keys are not read the same way.
hasTrustDialogAcceptedis resolved by walking up from the cwd (so one root entry does cover everything beneath it — the owner's ask works), buthasCompletedProjectOnboardingis read from the exact project key (xd()→projects[<canonical cwd>]) with no ancestor walk. A root-only write would therefore have removed the trust dialog and left the onboarding screen in front of every fresh checkout. So the two keys scope differently: trust on the root, onboarding always per spawn directory. That is also the concrete sense in which the owner's "what we are doing in this PR should still be kept" is load-bearing rather than a courtesy. - Guard rails (kept narrow, so the wider default cannot mean more than intended):
project_keys()still never widens on its own — it takes an explicitroot; a root that does not contain the spawn directory is ignored (is_within, component-wise so/wsdoes not "contain"/ws-other); and a root as broad as/or$HOMEdegrades to per-directory trust with a warning (is_too_broad). With no workspace root configured, both scopes behave identically. - Evidence: 10 new tests (root scope, sibling reuse, root-outside-cwd,
scope: directory, too-broad root,is_within/is_too_broadunits, config mapping);ruff+pyright+markdownlintclean, config validation VALID,pytest377 passed. - Next: awaiting the owner's review and the tier-4 security sign-off.
- Blockers: none.
2026-07-25 — rebased onto main again; decision renumbered 036 → 037
- Phase: needs-review
- Did:
maingained #95 (issue-93: route an event on a PR to its linked issue first), which claimeddecision-036too — an add/add collision, two different decisions with the same number. Since #95 is merged and this PR is not, theirs keeps 036 and this work item's decision is renumbered to 037, with every reference updated (spec, tasks, execution log,trust.py's module docstring, the decisions index, both capability docs andreference/automation.md). - Also refreshed the decision's title, which still read "trust the exact checkout" from before the owner's scope decision — it now says "trust the workspace root (configurable)", matching what actually shipped.
- Conflicts were all "both added a row/section" in shared docs (the decisions index,
automation.md, both capability docs) — resolved by keeping both entries. The two later commits also patcheddecision-036.mdby name, so their hunks were redirected intodecision-037.mdrather than resolved in place. - Evidence:
ruff+pyright+markdownlintclean, config validation VALID,pytest394 passed (the count rose because #95 brought its own tests along with the rebase). - Next: awaiting the owner's review and the tier-4 security sign-off.
- Blockers: none.