Skip to content

refactor(envmanifest): add sandbox environment contract manifest - #8679

Open
jflowers wants to merge 4 commits into
fullsend-ai:mainfrom
jflowers:opsx/sandbox-env-manifest
Open

jflowers wants to merge 4 commits into
fullsend-ai:mainfrom
jflowers:opsx/sandbox-env-manifest

Conversation

@jflowers

Copy link
Copy Markdown
Contributor

Summary

Codifies the sandbox environment-variable contract as a versioned, generated
manifest so runtimes and harness authors rely on a single normative artifact
instead of scattered prose. A new internal/envmanifest package defines the
v1 manifest types and a deterministic builder; a collector in internal/cli
executes the production emitters themselves (bootstrapEnv, per-iteration env,
trace/timeout vars, per-runtime EnvExports(), the Vertex scaffold env, and
the post-merge reserved-key set), so the manifest cannot drift from the code
that owns the surface.

Related Issue

Changes

Testing

  • make lint passes (stage changes first, then run)
  • Tests added/updated for new or modified logic
go test ./internal/cli -run TestEnvManifestGolden   # byte-compares committed manifest vs live emitters; -update regenerates
go test ./internal/envmanifest ./internal/cli       # builder + collector unit tests
cat docs/normative/sandbox-env/v1/sandbox-env.json  # the committed contract

Full suite go test -race -count=1 ./... green except 4 local environmental
failures (internal/forge/gitlab TLS CA, internal/repos /var-vs-/private/var,
internal/runtime flock ×2, plus an intermittent 5th in internal/scaffold) —
all baseline-verified pre-existing on clean main 3ec97b9; CI on Linux is
the arbiter. Patch coverage: internal/envmanifest 98% of statements
(target 80%, floor 75%).

Checklist

  • PR title follows Conventional Commits (correct type, ! for breaking changes)
  • Commits are signed off (DCO) — human and human-directed agent sessions only
  • I wrote this contribution myself and can explain all changes in it

I wrote this contribution with AI assistance and cannot explain all changes
in it.

This PR was generated by /uf.finale (AI-assisted).

@jflowers
jflowers requested a review from a team as a code owner October 21, 2026 20:15
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

E2E tests are running

Authorization passed for this commit. See the E2E Tests workflow for results.

@jflowers jflowers self-assigned this Oct 21, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Publish a generated sandbox environment contract manifest

✨ Enhancement 📝 Documentation 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Publish a versioned manifest of sandbox exports, conditional gates, runtime variables, and
 reserved keys.
• Derive the manifest from production emitters and byte-compare it in a golden test to detect drift.
• Document contract semantics and tag-pinned validation for downstream harness authors.
Diagram

graph TD
  Emitters["Runner and runtime emitters"] --> Collector["Test collector"] --> Builder["Manifest builder"] --> Manifest[("Versioned JSON")] --> Readers["Contract readers"]
  Other["Scaffold and reservations"] --> Collector
  Builder --> Golden["Golden test"] --> Manifest
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Expose runtime and export registries through production APIs
  • ➕ Avoids parsing runtime switch cases in a test.
  • ➕ Could make runtime enumeration and export discovery less fixture-dependent.
  • ➖ Changes production interfaces for a documentation artifact.
  • ➖ Still needs fixtures to distinguish conditional emissions.
2. Golden-test every emission gate independently
  • ➕ Provides stronger evidence that each documented gate matches runtime behavior.
  • ➖ Requires substantially more fixtures and maintenance.
  • ➖ Does not replace the need for a published, categorized manifest.

Recommendation: Keep the test-only collector and committed manifest: they add drift detection without changing sandbox behavior. Treat the hand-described gates and all-on/all-off fixtures as the main coverage boundary; expand gate-specific checks if those emission paths change.

Files changed (22) +1675 / -8

Enhancement (3) +303 / -0
sandbox-env.jsonCommit the generated v1 environment inventory +97/-0

Commit the generated v1 environment inventory

• Publishes eight always-on exports, nine conditional exports, four runtime groups, four harness-delivered variables, and 34 reserved keys as the machine-readable artifact.

docs/normative/sandbox-env/v1/sandbox-env.json

builder.goAssemble categorized environment contracts +79/-0

Assemble categorized environment contracts

• Adds a builder for universal, conditional, runtime-specific, harness-delivered, and reserved names. Building stamps the schema version and canonicalizes the result.

internal/envmanifest/builder.go

manifest.goDefine and serialize the v1 manifest +127/-0

Define and serialize the v1 manifest

• Introduces the typed schema, deterministic JSON rendering, parsing, and canonical name sets. Rendering enforces that harness-delivered entries are not labeled runner-guaranteed.

internal/envmanifest/manifest.go

Tests (4) +1029 / -0
env_manifest_collector_test.goDerive contract names from production emission paths +588/-0

Derive contract names from production emission paths

• Exercises bootstrap, iteration, trace, hook, runtime, and scaffold paths; reads the effective reserved map; and categorizes extracted names. Tests pin expected contents and compare runtime enumeration with the resolution registry.

internal/cli/env_manifest_collector_test.go

env_manifest_golden_test.goPin the committed manifest to collector output +92/-0

Pin the committed manifest to collector output

• Byte-compares the derived JSON against the normative file on ordinary test runs. Adds a '-update' regeneration path and a concise mismatch summary.

internal/cli/env_manifest_golden_test.go

builder_test.goTest builder canonicalization and empty categories +61/-0

Test builder canonicalization and empty categories

• Checks sorting, deduplication, explicit empty runtime exports, zero-value use, and serialization of empty categories.

internal/envmanifest/builder_test.go

manifest_test.goTest serialization and schema semantics +288/-0

Test serialization and schema semantics

• Covers exact JSON, repeatability, input-order independence, category shapes, parsing round trips, invalid input, and the names-only field inventory.

internal/envmanifest/manifest_test.go

Documentation (15) +343 / -8
0146-sandbox-environment-contract-manifest.mdRecord the sandbox contract decision +87/-0

Record the sandbox contract decision

• Explains the drift problem, generated-manifest decision, tag-based versioning, and intended cross-repository validation cadence. Distinguishes the committed contract from a future consumer gate.

docs/ADRs/0146-sandbox-environment-contract-manifest.md

architecture.mdLink sandbox architecture to the contract +7/-2

Link sandbox architecture to the contract

• Identifies environment variables and reserved keys as a decided, generated part of the sandbox contract while leaving tool and workspace setup open.

docs/architecture.md

run.mdPoint run CLI readers to the full environment inventory +5/-0

Point run CLI readers to the full environment inventory

• Links the existing iteration-variable documentation to the generated inventory of injected and reserved names.

docs/cli/run.md

runtime-implementation.mdAdd manifest maintenance to runtime guidance +12/-0

Add manifest maintenance to runtime guidance

• Adds collector and golden-test checks to the runtime implementation checklist, including the regeneration command. Clarifies that the generated contract supersedes prose tables for runner-written variables.

docs/contributing/runtime-implementation.md

cli-internals.mdConnect CLI environment internals to the manifest +6/-0

Connect CLI environment internals to the manifest

• Points readers of the bootstrap and iteration environment diagram to the machine-checkable inventory and its delivery categories.

docs/guides/dev/cli-internals.md

bring-your-own-agent.mdClarify Vertex variables are harness-delivered +1/-1

Clarify Vertex variables are harness-delivered

• Explains that the referenced Vertex environment file delivers variables through harness configuration, not a runner guarantee, and links their manifest category.

docs/guides/user/bring-your-own-agent.md

building-custom-agents.mdLink agent inputs to the environment contract +5/-0

Link agent inputs to the environment contract

• Directs custom-agent authors to the full inventory of universal, runtime-specific, and harness-delivered variables.

docs/guides/user/building-custom-agents.md

customizing-agents.mdReference the reserved-key inventory +3/-1

Reference the reserved-key inventory

• Links the warning about protected 'env.sandbox' variables to the generated reserved set.

docs/guides/user/customizing-agents.md

running-agents-locally.mdIdentify API-key override protection +3/-1

Identify API-key override protection

• Notes that 'OPENAI_API_KEY' appears in the published reserved set when explaining why it cannot be supplied through 'env.sandbox'.

docs/guides/user/running-agents-locally.md

README.mdSpecify the v1 contract and consumer rules +202/-0

Specify the v1 contract and consumer rules

• Defines category guarantees, JSON fields, tag-pinned fetch URLs, fail-closed parsing guidance, regeneration, and schema versioning. Explicitly separates runner exports from scaffold-delivered variables.

docs/normative/sandbox-env/v1/README.md

agent-infrastructure.mdMark the sandbox environment surface as specified +1/-1

Mark the sandbox environment surface as specified

• References the generated contract within the infrastructure resource-access discussion without treating other resource-access questions as resolved.

docs/problems/agent-infrastructure.md

security-threat-model.mdIdentify the manifest as a drift baseline +2/-0

Identify the manifest as a drift baseline

• Connects the published environment inventory to the threat model’s need for machine-checkable security-relevant baselines.

docs/problems/security-threat-model.md

harness-reference.mdLink harness overrides to the complete reserved set +1/-1

Link harness overrides to the complete reserved set

• Extends the timeout-variable reference with a link to all runner-injected variables and keys protected from 'env.sandbox' overrides.

docs/reference/harness-reference.md

claude.mdDocument Claude's manifest coverage +5/-0

Document Claude's manifest coverage

• Identifies 'CLAUDE_CONFIG_DIR' as Claude’s runtime-specific export and points to the contract for other exports and reserved keys.

docs/runtimes/claude.md

pi.mdDocument pi's manifest coverage +3/-1

Document pi's manifest coverage

• Links pi’s six runtime-specific exports and the runner’s reserved-key set from the Vertex troubleshooting guidance.

docs/runtimes/pi.md

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:17 PM UTC · Completed 8:39 PM UTC

Commit: e38def5 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $4.84

@qodo-code-review

qodo-code-review Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (2) 📜 Skill insights (2)

Grey Divider


Action required

1. The manifest ADR mixes decisions 📜 Skill insight ⚙ Maintainability
Description
ADR 0146's Decision section separately decides to publish a generated manifest, derive it from
production emitters with a golden test, version it by release tag, and impose PR and nightly
validation cadence. These are multiple distinct architectural decisions recorded in one ADR instead
of one decision with supporting implementation details.
Code

docs/ADRs/0146-sandbox-environment-contract-manifest.md[R51-54]

+Publish the sandbox environment contract as a generated, committed manifest
+at [`sandbox-env.json`](../normative/sandbox-env/v1/sandbox-env.json), with
+schema and category semantics in its [README](../normative/sandbox-env/v1/README.md).
+A collector derives it by executing the production emitters — the
Relevance

●● Moderate

ADR Decisions commonly include implementation details; no decisive precedent establishes this as
multiple decisions.

PR-#8158

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The ADR's Decision section contains separate commitments about how the contract is generated and
enforced, how it is versioned, and when consumers validate it. The checklist requires an ADR to
record exactly one decision, so these independent commitments need to be consolidated or split.

docs/ADRs/0146-sandbox-environment-contract-manifest.md[49-67]
Skill: writing-adrs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
ADR 0146 contains several distinct decisions in one Decision section: the manifest publication model, collector and golden-test enforcement, tag-based versioning, and cross-repository validation cadence.

## Fix Focus Areas
- docs/ADRs/0146-sandbox-environment-contract-manifest.md[49-67]

## Recommended Fix
Refocus ADR 0146 on one primary decision about the sandbox environment contract. Move implementation mechanics and validation cadence into supporting documentation, or create separate ADRs for materially independent versioning and cross-repository validation decisions, with explicit references between them.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


2. The new ADR falls outside the template ✓ Resolved
Description
The new ADR adds a ## References section that is not part of docs/ADRs/0000-adr-template.md's
required structure. Future readers and validation tooling encounter an extra top-level section
rather than the repository's prescribed ADR format.
Code

docs/ADRs/0146-sandbox-environment-contract-manifest.md[R81-84]

+## References
+
+- [ADR 0038](0038-universal-harness-access.md) — URL-based resource access and tag pinning
+- [ADR 0058](0058-agent-registration.md) — agent registration and the transitional fleet lockstep
Relevance

●● Moderate

An older precedent rejected adding a References section, but its age weakens confidence in current
enforcement.

PR-#2743

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires ADRs to follow the standard template's sections and format exactly. The new
ADR contains an additional top-level ## References section after ## Consequences, while the
template ends after Consequences and defines no References section.

docs/ADRs/0146-sandbox-environment-contract-manifest.md[81-87]
docs/ADRs/0000-adr-template.md[20-41]
Skill: writing-adrs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The ADR introduces a top-level `References` section that is not present in the repository ADR template, so the document does not follow the required structure.

## Fix Focus Areas
- docs/ADRs/0146-sandbox-environment-contract-manifest.md[81-87]

## Recommended Fix
Remove the standalone `## References` section and place any necessary ADR or issue links in the applicable Context, Decision, or Consequences prose using the template's existing sections.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. The ADR repeats the drift problem 📜 Skill insight ⚙ Maintainability
Description
The Context section reproduces several paragraphs of the environment-drift incident instead of
giving a brief summary with links to the existing problem documentation. Readers must maintain
duplicated details about allowlists, released runners, and the drift-control gap across the ADR and
threat-model material.
Code

docs/ADRs/0146-sandbox-environment-contract-manifest.md[R24-27]

+The sandbox environment surface — what the runner injects into the sandbox and
+reserves from harness override — was hand-synced in three descriptions, none
+machine-checked: the `reservedSandboxKeys` sync note in `internal/cli/run.go`,
+the agents repo's `SANDBOX_RUNTIME_VARS` allowlist in
Relevance

●●● Strong

Recent ADR precedent favors concise Context summaries linking existing problem documentation.

PR-#8348

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires ADR Context sections to link to problem docs with brief summaries rather than
reproduce their content. The added Context contains a multi-paragraph account of the hand-synced
surfaces, the incident, and the threat-model gap while only briefly linking to the threat model.

docs/ADRs/0146-sandbox-environment-contract-manifest.md[24-36]
Skill: writing-adrs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The ADR Context section restates detailed problem and incident material that belongs in the linked problem documentation, increasing the risk that the ADR and problem docs drift apart.

## Fix Focus Areas
- docs/ADRs/0146-sandbox-environment-contract-manifest.md[22-41]

## Recommended Fix
Replace the detailed incident narrative with a concise summary of the motivation and links to the relevant problem documents and incident references. Keep implementation details and the full threat-model discussion in their canonical documents.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


4. Pi exports fail manifest validation 🔗 Cross-repo conflict ≡ Correctness
Description
collectSandboxEnvManifest populates runtime_specific only from EnvExports(), omitting
launch-command exports such as FULLSEND_RUNTIME for codex and pi, and FULLSEND_PI_MANIFEST,
GOOGLE_CLOUD_PROJECT, and GOOGLE_CLOUD_LOCATION for pi. When the agents-repo reachability gate
checks skills against the manifest, it can reject references to variables the runner supplies,
including $FULLSEND_RUNTIME in bootstrap-guided skills and GOOGLE_CLOUD_LOCATION for pi on
Vertex; gateway-route exports are also absent from the inventory.
Code

docs/normative/sandbox-env/v1/sandbox-env.json[R33-35]

+      "pi": [
+        "JITI_FS_CACHE",
+        "PI_CODING_AGENT_DIR",
Relevance

●●● Strong

The manifest omits runner-exported runtime variables, undermining the stated reachability contract.

PR-#8300
PR-#8590

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The collector reads only backend.EnvExports() for each runtime, while the codex and pi launch
builders export additional variables into the agent shell. Codex golden launch files show `export
FULLSEND_RUNTIME=codex, and the pi run test asserts exports of FULLSEND_RUNTIME`,
GOOGLE_CLOUD_LOCATION, and GOOGLE_CLOUD_PROJECT after EnvExports(). The bootstrap prompts
direct agents to use FULLSEND_RUNTIME, and the agents documentation identifies
GOOGLE_CLOUD_LOCATION as runner-provided for pi on Vertex; neither appears in the committed
runtime-specific manifest lists. The README describes the inventory as runner-set exports, making
the omission relevant to consumers checking skill references against it.

fullsend -> agents
internal/cli/env_manifest_collector_test.go[332-337]
internal/runtime/pi_run.go[498-507]
docs/normative/sandbox-env/v1/sandbox-env.json[33-40]
internal/runtime/pi_run.go[57-57]
internal/runtime/codex_run.go[36-36]
internal/runtime/pi_run_test.go[105-109]
internal/runtime/testdata/codex/golden/launch-nohooks.txt[25-29]
internal/runtime/pi_bootstrap.go[464-464]
internal/runtime/claude_gateway.go[349-355]
internal/cli/env_manifest_collector_test.go[333-336]
internal/runtime/codex_run.go[400-409]
internal/runtime/pi_run.go[498-521]
docs/normative/sandbox-env/v1/README.md[113-119]
docs/normative/sandbox-env/v1/sandbox-env.json[25-41]
External repo: fullsend-ai/agents, LOCAL.md [280-285]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The sandbox env manifest collects runtime `EnvExports()` but omits variables exported by runtime launch commands, so its published inventory can under-report variables available to skills. Account for codex and pi launch exports and gateway-route exports, including their applicable gates.

## Fix Focus Areas
- internal/cli/env_manifest_collector_test.go[332-337]
- internal/runtime/codex_run.go[400-409]
- internal/runtime/pi_run.go[498-521]
- docs/normative/sandbox-env/v1/sandbox-env.json[25-41]
- docs/normative/sandbox-env/v1/README.md[87-101]
- /cross_repos/agents/LOCAL.md[280-285]

## Recommended Fix
Extend collection and its assertions to include export names from each production runtime's launch command alongside `EnvExports()`, using the runtime launch builders as the source where practical. Include `FULLSEND_RUNTIME`, the applicable pi exports, and gateway-only variables with accurate gate descriptions, preserving the Vertex gate for `GOOGLE_CLOUD_PROJECT`. Regenerate the manifest with `-update` and keep its README and the agents documentation aligned with what the inventory covers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


5. Fleet exports fail manifest validation 🔗 Cross-repo conflict ≡ Correctness
Description
harness_delivered is derived only from the Vertex scaffold, so it omits GOOGLE_CLOUD_PROJECT and
ANTHROPIC_DEFAULT_SONNET_MODEL even though the agents fleet delivers both through
env/gcp-vertex.env. When agents switches its reachability check to this manifest, references to
those fleet-delivered variables will appear unreachable.
Code

docs/normative/sandbox-env/v1/sandbox-env.json[R43-45]

+  "harness_delivered": {
+    "ANTHROPIC_VERTEX_PROJECT_ID": {
+      "provenance": "fullsend agent new Vertex scaffold (internal/agentnew); the fleet delivers the same variables through env/gcp-vertex.env in fullsend-ai/agents",
Relevance

●●● Strong

The committed harness inventory omits fleet-delivered variables explicitly identified in its
provenance source.

PR-#8377

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR publishes four harness-delivered names and says the fleet delivers them through its shared
file. That file also exports the two omitted names; an agents harness mounts it, and agents
documentation describes one omitted name as a fleet-wide setting.

fullsend -> agents
docs/normative/sandbox-env/v1/sandbox-env.json[43-60]
internal/cli/env_manifest_collector_test.go[428-443]
External repo: fullsend-ai/agents, env/gcp-vertex.env [1-17]
External repo: fullsend-ai/agents, harness/review.yaml [27-35]
External repo: fullsend-ai/agents, docs/review.md [128]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The published harness-delivered inventory covers only the Vertex scaffold, while the agents fleet delivers additional variables through its shared environment file.

## Fix Focus Areas
- docs/normative/sandbox-env/v1/sandbox-env.json[43-60]
- internal/cli/env_manifest_collector_test.go[428-443]
- docs/normative/sandbox-env/v1/README.md[103-111]
- /cross_repos/agents/env/gcp-vertex.env[1-17]

## Recommended Fix
Either include the additional fleet-delivered names with accurate provenance and a cross-repo check, or narrow the documented inventory explicitly to scaffold-delivered variables so consumers do not treat it as an exhaustive fleet contract.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


View medium (3)
6. New runtimes pass the golden check ✓ Resolved
Description
The README says registering a runtime without a manifest decision fails TestEnvManifestGolden, but
that test collects only the hard-coded productionSandboxRuntimes list. A new registry case leaves
its golden output unchanged; only the separate TestEnvManifestRuntimeEnumeration detects it, so
the documented golden-only command does not provide the stated safeguard.
Code

docs/normative/sandbox-env/v1/README.md[R98-101]

+The enumerated runtimes are the runner's resolution registry minus test
+stubs. A runtime that exports nothing appears with an empty set — `opencode`
+— rather than being omitted. Registering a new runtime without a manifest
+decision fails the golden test.
Relevance

●●● Strong

The documented golden-test guarantee omits the separate runtime-enumeration safeguard, creating a
concrete documentation mismatch.

PR-#8377

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The golden test calls the collector but not the enumeration test. The collector loops over a fixed
list, while registry comparison occurs only in a separately named test.

internal/cli/env_manifest_golden_test.go[30-33]
internal/cli/env_manifest_collector_test.go[34-45]
internal/cli/env_manifest_collector_test.go[232-249]
internal/cli/env_manifest_collector_test.go[333-336]
docs/normative/sandbox-env/v1/README.md[98-101]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The documented golden-only test passes after a runtime is registered without updating the collector's runtime list.
## Fix Focus Areas
- internal/cli/env_manifest_golden_test.go[30-33]
- internal/cli/env_manifest_collector_test.go[232-249]
- docs/normative/sandbox-env/v1/README.md[98-101]
## Recommended Fix
Run the registry-enumeration invariant from the golden check, preferably through a shared helper, so the documented `-run TestEnvManifestGolden` command fails on a newly registered runtime.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Deadline and trace variables can be absent ✓ Resolved
Description
collectSandboxEnvManifest puts the three iteration-file exports in always based on a successful
iterationEnvCommand probe. If writing that file fails, the runner warns, clears it, and continues
to rt.Run; the agent can therefore start without the advertised timeout, deadline, or trace
variable.
Code

internal/cli/env_manifest_collector_test.go[R312-315]

+	iterationCmd := iterationEnvCommand(30, time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), envManifestTraceparent)
+	for _, key := range parseExportedEnvKeys(iterationCmd) {
+		require.False(t, conditional[key], "%q is emitted both under a gate and unconditionally", key)
+		b.AddAlways(key)
Relevance

●●● Strong

The collector labels best-effort iteration exports as unconditional despite production continuing
after write failure.

PR-#8586
PR-#8300

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new collector classifies the names as always present, but production explicitly continues after
clearing the file on write failure. The bootstrap source line skips the missing file, so it does not
restore those exports.

internal/cli/env_manifest_collector_test.go[312-316]
internal/cli/run.go[2542-2570]
internal/cli/run.go[3465-3468]
docs/normative/sandbox-env/v1/README.md[76-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The manifest calls iteration-file variables unconditional although a failed write does not stop the agent iteration.
## Fix Focus Areas
- internal/cli/env_manifest_collector_test.go[312-316]
- internal/cli/env_manifest_collector_test.go[48-63]
- docs/normative/sandbox-env/v1/sandbox-env.json[4-24]
## Recommended Fix
Classify the three iteration-file names as conditional on a successful write, add their gate descriptions and collector assertions, then regenerate the manifest.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. The ADR hides a related decision link ✓ Resolved
Description
The ADR cites ADR 0038 only in its Decision section even though the manifest's tag-pinned fetch
mechanism builds on that earlier decision. Because the related link is absent from Context,
readers reviewing the motivation and architectural dependencies must search beyond the required
context section.
Code

docs/ADRs/0146-sandbox-environment-contract-manifest.md[R63-67]

+The manifest is fetchable at any tag via raw.githubusercontent.com — the pinned-URL
+mechanism of [ADR 0038](0038-universal-harness-access.md) — and the tag is the
+version: no `fullsend_version` field. Consumers validate env-var reachability
+against the latest released tag at PR time and `main` nightly, following the
+[ADR 0139](0139-agent-functional-test-suite-lives-in-agents-repo.md) bridge.
Relevance

●●● Strong

Recent ADR precedent explicitly adds related architectural links to Context for reader orientation.

PR-#8158
PR-#8348

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires an ADR's Context section to explicitly cross-reference related ADRs. The
Context links ADRs 0058 and 0139, but ADR 0038 is introduced as the basis for the tag-pinned fetch
mechanism only in the later Decision section.

docs/ADRs/0146-sandbox-environment-contract-manifest.md[22-41]
docs/ADRs/0146-sandbox-environment-contract-manifest.md[63-67]
Skill: writing-adrs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
ADR 0038 is a related architectural decision for the tag-pinned manifest URL mechanism, but it is referenced only in the Decision section rather than explicitly cross-referenced from Context.

## Fix Focus Areas
- docs/ADRs/0146-sandbox-environment-contract-manifest.md[22-67]

## Recommended Fix
Add an explicit Markdown link to ADR 0038 in the Context section near the explanation of the existing tag-pinned resource-access mechanism, while retaining the detailed implementation reference in Decision if useful.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

9. Rendering can mutate the caller's gate map ✓ Resolved
Description
Manifest.normalized() copies and sorts every other category, but Conditional reuses the caller's
map as-is. The doc comment's claim that "m itself is not modified" therefore holds only because
nothing writes to the result today. A later edit to the normalized copy (for example, trimming gate
descriptions) would silently change the builder's or parsed manifest's map. Reusing the builder
after Build() has the same problem: further AddConditional calls also change manifests that were
already built.
Code

internal/envmanifest/manifest.go[R89-92]

+	conditional := m.Exports.Conditional
+	if conditional == nil {
+		conditional = map[string]string{}
+	}
Relevance

●●● Strong

Defensive copying prevents builder reuse and future normalized-map mutations from altering
caller-owned manifests.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
runtimeSpecific and harnessDelivered are rebuilt into fresh maps, and slices go through
sortedUnique. conditional := m.Exports.Conditional is the only alias. Builder.Build passes
b.conditional directly into the manifest.

internal/envmanifest/manifest.go[84-112]
internal/envmanifest/builder.go[68-79]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`normalized()` aliases `m.Exports.Conditional` rather than copying it, which contradicts its own doc comment.

## Fix Focus Areas
- internal/envmanifest/manifest.go[89-92]

## Recommended Fix
Replace the nil check with `conditional := maps.Clone(m.Exports.Conditional); if conditional == nil { conditional = map[string]string{} }`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Infrastructure problem lacks options ✓ Resolved
Description
docs/problems/agent-infrastructure.md adds a prescribed sandbox contract reference without
describing alternative approaches or their trade-offs. The modified open-questions section still
contains questions and decisions rather than two analyzed solution options, so readers cannot
compare alternatives in this core problem document.
Code

docs/problems/agent-infrastructure.md[124]

+- How do we provide agents with “resources to do their work” (clone, tools, APIs) without over-privileging them or creating a single high-value target? (Credential access decided in [ADR 0017](../ADRs/0017-credential-isolation-for-sandboxed-agents.md), with [ADR 0092](../ADRs/0092-openai-wif-credential-delivery.md) as the first inference credential delivered entirely through a gateway provider, and [ADR 0137](../ADRs/0137-inference-gateway-credential-route.md) routing inference through an LLM gateway that accepts the job's OIDC token so the forge stores no reusable provider key ([ADR 0138](../ADRs/0138-inference-gateway-api-key-credential-mode.md) adds an opt-in API-key mode for gateways that cannot trust forge OIDC, at the cost of a long-lived secret); tool access restrictions decided in [ADR 0027](../ADRs/0027-allowed-and-disallowed-tools-for-agents.md); cross-org public GitHub Packages access decided in [ADR 0114](../ADRs/0114-github-packages-via-host-bound-workflow-token-provider.md); the sandbox environment the runner provides — which env vars it injects, per runtime, and which keys it reserves from harness override — is now published as a generated, machine-checkable contract ([ADR 0146](../ADRs/0146-sandbox-environment-contract-manifest.md)); other resource access remains open.)
Relevance

● Weak

Recent precedent rejected requiring multiple options in a security problem document for a prescribed
defense.

PR-#7411

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The modified core problem document adds the sandbox contract to an open-question paragraph, but the
document has no option analysis or trade-off subsections. This fails the requirement that every new
or modified core problem document present multiple options with clearly labeled trade-offs.

Rule 1062035: Problem docs must present multiple options with trade-offs, not a single prescribed solution
docs/problems/agent-infrastructure.md[121-130]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The core agent-infrastructure problem document was modified without documenting at least two distinct solution options and their trade-offs.

## Fix Focus Areas
- docs/problems/agent-infrastructure.md[121-130]

## Recommended Fix
Add two clearly labeled solution-option subsections that describe the approaches and their advantages, disadvantages, or risks. If the generated sandbox environment contract is the preferred approach, retain the alternatives and state the recommendation only after comparing them.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Threat model problem lacks options ✓ Resolved
Description
docs/problems/security-threat-model.md adds the generated manifest as the answer to
environment-surface drift without describing alternative approaches or their trade-offs. The
modified section presents one prescribed mechanism and then returns to general drift concerns, so
the core problem document lacks the required comparison of solutions.
Code

docs/problems/security-threat-model.md[175]

+The runner's environment surface now has such a baseline: which env vars the sandbox receives, per runtime and under documented gates, is published as a generated, machine-checkable manifest rather than hand-synced descriptions, so content repos can verify env-var reachability against a released runner ([ADR 0146](../ADRs/0146-sandbox-environment-contract-manifest.md)).
Relevance

● Weak

Recent precedent rejected requiring options analysis when a threat-model section documents a
prescribed defense.

PR-#7411

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The modified core problem document adds a single generated-manifest approach for the runner
environment baseline, but the document contains no corresponding option comparison or trade-off
analysis. That matches the checklist's failure condition for a modified problem document prescribing
one solution without alternatives.

Rule 1062035: Problem docs must present multiple options with trade-offs, not a single prescribed solution
docs/problems/security-threat-model.md[170-181]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The core security-threat-model problem document was modified without documenting at least two distinct solution options and their trade-offs.

## Fix Focus Areas
- docs/problems/security-threat-model.md[170-181]

## Recommended Fix
Add two clearly labeled approaches for establishing and checking security-relevant baselines, with explicit advantages, disadvantages, and risks for each. Present the generated sandbox manifest as a recommendation only after the alternatives have been analyzed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
❌ Tickets: failed to load — Ticket collection could not establish complete evidence
✅ Compliance rules (platform): 95 rules
✅ Skills: 5 invoked
  writing-user-docs
  writing-adrs
  check-patch-coverage
  cutting-releases
  adr-corner
✅ Cross-repo context — repo relationships
  Explored: repo: fullsend-ai/agents (sha: cf464e2e) — View relationship
Review mode: Auto: 🧠 Deep: Complex contract generation spans multiple emitters, runtimes, tests, and security-sensitive reservations.

Grey Divider

Tip of the day
💡 Did you know, you can describe a rule in plain language on the Rules page and Qodo drafts it for you

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +51 to +54
Publish the sandbox environment contract as a generated, committed manifest
at [`sandbox-env.json`](../normative/sandbox-env/v1/sandbox-env.json), with
schema and category semantics in its [README](../normative/sandbox-env/v1/README.md).
A collector derives it by executing the production emitters — the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. The manifest adr mixes decisions 📜 Skill insight ⚙ Maintainability

ADR 0146's Decision section separately decides to publish a generated manifest, derive it from
production emitters with a golden test, version it by release tag, and impose PR and nightly
validation cadence. These are multiple distinct architectural decisions recorded in one ADR instead
of one decision with supporting implementation details.
Agent Prompt
## Issue description
ADR 0146 contains several distinct decisions in one Decision section: the manifest publication model, collector and golden-test enforcement, tag-based versioning, and cross-repository validation cadence.

## Fix Focus Areas
- docs/ADRs/0146-sandbox-environment-contract-manifest.md[49-67]

## Recommended Fix
Refocus ADR 0146 on one primary decision about the sandbox environment contract. Move implementation mechanics and validation cadence into supporting documentation, or create separate ADRs for materially independent versioning and cross-repository validation decisions, with explicit references between them.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

Comment on lines +24 to +27
The sandbox environment surface — what the runner injects into the sandbox and
reserves from harness override — was hand-synced in three descriptions, none
machine-checked: the `reservedSandboxKeys` sync note in `internal/cli/run.go`,
the agents repo's `SANDBOX_RUNTIME_VARS` allowlist in

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. The adr repeats the drift problem 📜 Skill insight ⚙ Maintainability

The Context section reproduces several paragraphs of the environment-drift incident instead of
giving a brief summary with links to the existing problem documentation. Readers must maintain
duplicated details about allowlists, released runners, and the drift-control gap across the ADR and
threat-model material.
Agent Prompt
## Issue description
The ADR Context section restates detailed problem and incident material that belongs in the linked problem documentation, increasing the risk that the ADR and problem docs drift apart.

## Fix Focus Areas
- docs/ADRs/0146-sandbox-environment-contract-manifest.md[22-41]

## Recommended Fix
Replace the detailed incident narrative with a concise summary of the motivation and links to the relevant problem documents and incident references. Keep implementation details and the full threat-model discussion in their canonical documents.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

Comment thread docs/ADRs/0146-sandbox-environment-contract-manifest.md Outdated
Comment thread docs/ADRs/0146-sandbox-environment-contract-manifest.md Outdated
Comment thread internal/cli/env_manifest_collector_test.go Outdated
Comment thread docs/normative/sandbox-env/v1/README.md Outdated
Comment thread internal/envmanifest/manifest.go Outdated
Comment on lines +43 to +45
"harness_delivered": {
"ANTHROPIC_VERTEX_PROJECT_ID": {
"provenance": "fullsend agent new Vertex scaffold (internal/agentnew); the fleet delivers the same variables through env/gcp-vertex.env in fullsend-ai/agents",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

7. Fleet exports fail manifest validation 🔗 Cross-repo conflict ≡ Correctness

harness_delivered is derived only from the Vertex scaffold, so it omits GOOGLE_CLOUD_PROJECT and
ANTHROPIC_DEFAULT_SONNET_MODEL even though the agents fleet delivers both through
env/gcp-vertex.env. When agents switches its reachability check to this manifest, references to
those fleet-delivered variables will appear unreachable.
Agent Prompt
## Issue description
The published harness-delivered inventory covers only the Vertex scaffold, while the agents fleet delivers additional variables through its shared environment file.

## Fix Focus Areas
- docs/normative/sandbox-env/v1/sandbox-env.json[43-60]
- internal/cli/env_manifest_collector_test.go[428-443]
- docs/normative/sandbox-env/v1/README.md[103-111]
- /cross_repos/agents/env/gcp-vertex.env[1-17]

## Recommended Fix
Either include the additional fleet-delivered names with accurate provenance and a cross-repo check, or narrow the documented inventory explicitly to scaffold-delivered variables so consumers do not treat it as an exhaustive fleet contract.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

Comment on lines +33 to +35
"pi": [
"JITI_FS_CACHE",
"PI_CODING_AGENT_DIR",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

8. Pi exports fail manifest validation 🔗 Cross-repo conflict ≡ Correctness

collectSandboxEnvManifest populates runtime_specific only from EnvExports(), omitting
launch-command exports such as FULLSEND_RUNTIME for codex and pi, and FULLSEND_PI_MANIFEST,
GOOGLE_CLOUD_PROJECT, and GOOGLE_CLOUD_LOCATION for pi. When the agents-repo reachability gate
checks skills against the manifest, it can reject references to variables the runner supplies,
including $FULLSEND_RUNTIME in bootstrap-guided skills and GOOGLE_CLOUD_LOCATION for pi on
Vertex; gateway-route exports are also absent from the inventory.
Agent Prompt
## Issue description
The sandbox env manifest collects runtime `EnvExports()` but omits variables exported by runtime launch commands, so its published inventory can under-report variables available to skills. Account for codex and pi launch exports and gateway-route exports, including their applicable gates.

## Fix Focus Areas
- internal/cli/env_manifest_collector_test.go[332-337]
- internal/runtime/codex_run.go[400-409]
- internal/runtime/pi_run.go[498-521]
- docs/normative/sandbox-env/v1/sandbox-env.json[25-41]
- docs/normative/sandbox-env/v1/README.md[87-101]
- /cross_repos/agents/LOCAL.md[280-285]

## Recommended Fix
Extend collection and its assertions to include export names from each production runtime's launch command alongside `EnvExports()`, using the runtime launch builders as the source where practical. Include `FULLSEND_RUNTIME`, the applicable pi exports, and gateway-only variables with accurate gate descriptions, preserving the Vertex gate for `GOOGLE_CLOUD_PROJECT`. Regenerate the manifest with `-update` and keep its README and the agents documentation aligned with what the inventory covers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Oct 21, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, high churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, high churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (2)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, high churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (3)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, high churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (4)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, high churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (5)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, high churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (6)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (7)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn across touched documentation files, and full alignment with an active linked issue.

Previous run (8)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn across touched documentation files, and full alignment with an active linked issue.

Previous run (9)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and multi-author contention across touched documentation files, and full alignment with an active linked issue.

Previous run (10)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Additive contract manifest package and tests with no protected path or dependency changes, high churn across touched doc files, and well-scoped issue alignment yield a moderate risk score.

Previous run (11)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Additive contract manifest package and tests with no protected path or dependency changes, high churn across touched doc files, and well-scoped issue alignment yield a moderate risk score.

Previous run (12)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Additive contract manifest package and tests with no protected path or dependency changes, high churn across touched doc files, and well-scoped issue alignment yield a moderate risk score.

Previous run (13)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (14)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (15)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, offset by elevated churn and coupling on existing documentation files, and full alignment with an active linked issue.

Previous run (16)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (17)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, high churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (18)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, high churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (19)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (20)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (21)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (22)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (23)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (24)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, elevated churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (25)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, high churn and change coupling across touched documentation files, and full alignment with an active linked issue.

Previous run (26)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Large additive change with no protected paths or workflow edits, high churn and change coupling across touched documentation files, and full alignment with an active linked issue.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Review

Findings

Medium

  • [api-contract] internal/cli/env_manifest_collector_test.go:395 — The collector omits variables exported by runtime launch commands outside EnvExports() (e.g. FULLSEND_RUNTIME, FULLSEND_PI_MANIFEST, GOOGLE_CLOUD_LOCATION from the pi launch command; FULLSEND_RUNTIME from codex/opencode). None appears in the committed manifest and changes to these emitters cannot affect the golden result, so the inventory of runner-injected variables is incomplete and downstream reachability checks would reject valid references.
    Remediation: Collect the launch-command exports with their runtime and feature conditions, or explicitly narrow the contract and provide a separate inventory.
  • [test-inadequate] internal/cli/env_manifest_collector_test.go:72 — envManifestKeyRe matches KEY= assignments rather than exported variables, so dropping export leaves the manifest and golden test unchanged, and assignment-like text inside quoted values (e.g. ?PHANTOM=value) yields a phantom variable.
    Remediation: Use export-aware parsing or inspect the resulting exported environment, and add regression tests for both cases.

Low

  • [api-contract] internal/cli/env_manifest_collector_test.go:62 — Hook-variable gates (TIRITH_REQUIRED, TIRITH_FAIL_ON, FULLSEND_EGRESS_ALLOWLIST) omit runtime support; only Claude's bootstrap is exercised and opencode never calls appendHookEnv (it fails closed when hooks are enabled, so impact is small).
    Remediation: Add the supported-runtime restriction (claude, codex, pi) to the gate descriptions or document the opencode fail-closed behavior.
  • [incorrect-doc] docs/normative/sandbox-env/v1/README.md:101 — Says opencode appears with an empty export set, but sandbox-env.json lists three opencode exports.
    Remediation: Remove the opencode example or state its current exports; fix the matching collector comment.
  • [incorrect-doc] docs/contributing/runtime-implementation.md:67 — Says registering a runtime without a manifest decision fails the golden test; it fails TestEnvManifestRuntimeEnumeration.
    Remediation: Name TestEnvManifestRuntimeEnumeration for registration drift and TestEnvManifestGolden -update for regeneration.
  • [pattern-inconsistency] docs/contributing/runtime-implementation.md:64 — New checklist item uses - [ ] while every other touchpoint uses - [x].
    Remediation: Change to - [x].
  • [doc-style] docs/normative/sandbox-env/v1/README.md:121 — Line is 105 characters; wrap prose at ~80 columns.
    Remediation: Wrap the sentence.

Earlier findings

  • docs/ADRs/0146-sandbox-environment-contract-manifest.md, architectural-conflict — resolved; ADR now has the Forge scope section and commit-pinned citations.
  • internal/cli/env_manifest_collector_test.go:402, under-engineering — resolved; collector probes gated-on/off emitters and merges reserved keys.
  • internal/cli/env_manifest_collector_test.go:297, design-smell — resolved; runtime enumeration test binds the contract to the registry.
  • internal/cli/env_manifest_collector_test.go:565, complexity-ratio — resolved; test scaffolding is proportionate.
  • internal/cli/env_manifest_collector_test.go:88, over-engineering — resolved; assertions are proportionate.
  • internal/cli/env_manifest_collector_test.go:62, api-contract — reclassified to low (opencode fails closed when hooks are enabled).
  • internal/cli/env_manifest_collector_test.go:395, logic-error — reclassified to api-contract (launch-command exports omitted, see above).

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

<!-- fullsend:review-findings-v2:eyJ2ZXJzaW9uIjoyLCJhY3Rpb24iOiJyZXF1ZXN0LWNoYW5nZXMiLCJmaW5kaW5ncyI6W3sic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImxvZ2ljLWVycm9yIiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMmU4MGE3ZTdlNjE0ZGU2MSIsImxpbmUiOjM5NSwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6Im1lZGl1bSIsImNhdGVnb3J5IjoiYXBpLWNvbnRyYWN0IiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMWJiZDI3MmFiNTFiODRmMyIsImxpbmUiOjYyLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibWVkaXVtIiwiY2F0ZWdvcnkiOiJ0ZXN0LWluYWRlcXVhdGUiLCJmaWxlIjoiaW50ZXJuYWwvY2xpL2Vudl9tYW5pZmVzdF9jb2xsZWN0b3JfdGVzdC5nbyIsImlkIjoiZl83Y2ZjMzM5NTIyNWQyMjQwIiwibGluZSI6NzIsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6InVuZGVyLWVuZ2luZWVyaW5nIiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMzNiNTE4NzVkNzg4OTljNCIsImxpbmUiOjQwMiwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImFyY2hpdGVjdHVyYWwtY29uZmxpY3QiLCJmaWxlIjoiZG9jcy9BRFJzLzAxNDYtc2FuZGJveC1lbnZpcm9ubWVudC1jb250cmFjdC1tYW5pZmVzdC5tZCIsImlkIjoiZl9jNjUwMTQxODRmMTMxY2Q2IiwibGluZSI6MSwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6ImluY29ycmVjdC1kb2MiLCJmaWxlIjoiZG9jcy9ub3JtYXRpdmUvc2FuZGJveC1lbnYvdjEvUkVBRE1FLm1kIiwiaWQiOiJmXzljZTE0ZmE2ZjE3NjI5ZDMiLCJsaW5lIjoxMDEsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6ImluY29ycmVjdC1kb2MiLCJmaWxlIjoiZG9jcy9jb250cmlidXRpbmcvcnVudGltZS1pbXBsZW1lbnRhdGlvbi5tZCIsImlkIjoiZl8xMDRiMjhjMmExODMzZjMzIiwibGluZSI6NjcsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6InBhdHRlcm4taW5jb25zaXN0ZW5jeSIsImZpbGUiOiJkb2NzL2NvbnRyaWJ1dGluZy9ydW50aW1lLWltcGxlbWVudGF0aW9uLm1kIiwiaWQiOiJmXzBjZTg0N2QwNWQ5NjExOTIiLCJsaW5lIjo2NCwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6ImxvdyIsImNhdGVnb3J5IjoiZG9jLXN0eWxlIiwiZmlsZSI6ImRvY3Mvbm9ybWF0aXZlL3NhbmRib3gtZW52L3YxL1JFQURNRS5tZCIsImlkIjoiZl84OTg0MjNhYTVlOGNlNWRlIiwibGluZSI6MTIxLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibG93IiwiY2F0ZWdvcnkiOiJkZXNpZ24tc21lbGwiLCJmaWxlIjoiaW50ZXJuYWwvY2xpL2Vudl9tYW5pZmVzdF9jb2xsZWN0b3JfdGVzdC5nbyIsImlkIjoiZl8xMjZjMzIyZTQxZTg2OWZkIiwibGluZSI6Mjk3LCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6ImxvdyIsImNhdGVnb3J5IjoiY29tcGxleGl0eS1yYXRpbyIsImZpbGUiOiJpbnRlcm5hbC9jbGkvZW52X21hbmlmZXN0X2NvbGxlY3Rvcl90ZXN0LmdvIiwiaWQiOiJmXzlhZjA2MGY5ZjU3OTMwMjEiLCJsaW5lIjo1NjUsImFjdGlvbmFibGUiOmZhbHNlfSx7InNldmVyaXR5IjoibG93IiwiY2F0ZWdvcnkiOiJvdmVyLWVuZ2luZWVyaW5nIiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfN2YyYzkwZDY4ZjM3NzE1NSIsImxpbmUiOjg4LCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6ImluZm8iLCJjYXRlZ29yeSI6ImluY29tcGxldGUtZG9jIiwiZmlsZSI6ImRvY3MvcnVudGltZXMvY29kZXgubWQiLCJpZCI6ImZfOGUxYTNjN2E5ZDkyMzU5YiIsImFjdGlvbmFibGUiOmZhbHNlfSx7InNldmVyaXR5IjoiaW5mbyIsImNhdGVnb3J5IjoiaW5jb21wbGV0ZS1kb2MiLCJmaWxlIjoiZG9jcy9ydW50aW1lcy9vcGVuY29kZS5tZCIsImlkIjoiZl8xZmUyNTIxMDhiZmMxYWI5IiwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJpbmZvIiwiY2F0ZWdvcnkiOiJiYWNrd2FyZC1pbmNvbXBhdGlibGUiLCJmaWxlIjoiZG9jcy9ub3JtYXRpdmUvc2FuZGJveC1lbnYvdjEvUkVBRE1FLm1kIiwiaWQiOiJmXzRmMGI2ZTc1YjE5MTEwMGIiLCJsaW5lIjoyMDgsImFjdGlvbmFibGUiOmZhbHNlfSx7InNldmVyaXR5IjoiaW5mbyIsImNhdGVnb3J5IjoibWlzc2luZy1kZXByZWNhdGlvbiIsImZpbGUiOiJkb2NzL25vcm1hdGl2ZS9zYW5kYm94LWVudi92MS9SRUFETUUubWQiLCJpZCI6ImZfOThlYjVlNjMzYzQ4ZDc0NiIsImxpbmUiOjEyNiwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJpbmZvIiwiY2F0ZWdvcnkiOiJkb2Mtc3R5bGUiLCJmaWxlIjoiZG9jcy9wcm9ibGVtcy9hZ2VudC1pbmZyYXN0cnVjdHVyZS5tZCIsImlkIjoiZl8xODQ5YmQ1ODI1YjM1ZmI5IiwibGluZSI6MTI0LCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6ImluZm8iLCJjYXRlZ29yeSI6ImFwaS1zaGFwZSIsImZpbGUiOiJpbnRlcm5hbC9lbnZtYW5pZmVzdC9idWlsZGVyLmdvIiwiaWQiOiJmX2FmYzQ3ZGEwNTFkYmI4YmYiLCJsaW5lIjo3NiwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImFwaS1jb250cmFjdCIsImZpbGUiOiJpbnRlcm5hbC9jbGkvZW52X21hbmlmZXN0X2NvbGxlY3Rvcl90ZXN0LmdvIiwiaWQiOiJmXzYxMGQ5ODZlZDJlZWE3YjgiLCJsaW5lIjo2MiwiYWN0aW9uYWJsZSI6dHJ1ZX1dLCJkaXNwb3NpdGlvbnMiOlt7ImlkIjoiZl8yZTgwYTdlN2U2MTRkZTYxIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzFiYmQyNzJhYjUxYjg0ZjMiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfN2NmYzMzOTUyMjVkMjI0MCIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl85Y2UxNGZhNmYxNzYyOWQzIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzEwNGIyOGMyYTE4MzNmMzMiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfMGNlODQ3ZDA1ZDk2MTE5MiIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl84OTg0MjNhYTVlOGNlNWRlIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzhlMWEzYzdhOWQ5MjM1OWIiLCJzdGF0dXMiOiJyZWNsYXNzaWZpZWQifSx7ImlkIjoiZl8xZmUyNTIxMDhiZmMxYWI5Iiwic3RhdHVzIjoicmVjbGFzc2lmaWVkIn0seyJpZCI6ImZfNGYwYjZlNzViMTkxMTAwYiIsInN0YXR1cyI6InJlY2xhc3NpZmllZCJ9LHsiaWQiOiJmXzk4ZWI1ZTYzM2M0OGQ3NDYiLCJzdGF0dXMiOiJyZWNsYXNzaWZpZWQifSx7ImlkIjoiZl8xODQ5YmQ1ODI1YjM1ZmI5Iiwic3RhdHVzIjoicmVjbGFzc2lmaWVkIn0seyJpZCI6ImZfYWZjNDdkYTA1MWRiYjhiZiIsInN0YXR1cyI6InJlY2xhc3NpZmllZCJ9LHsiaWQiOiJmXzYxMGQ5ODZlZDJlZWE3YjgiLCJzdGF0dXMiOiJyZXNvbHZlZF9ieV9jaGFuZ2UifV19 -->

Review

Findings

Medium

  • [logic-error] internal/cli/env_manifest_collector_test.go:395 — The collector gathers runtime exports only through EnvExports(), so variables exported by the runtime launch commands never reach the manifest. FULLSEND_RUNTIME is exported at launch by the pi, codex and opencode runtimes, and FULLSEND_PI_MANIFEST by pi. None of them appears in sandbox-env.json, and changing them does not fail the golden test.
    Remediation: Collect the production runtime launch emitters as well as EnvExports(), including gated branches, and represent those exports in the contract. Add assertions for FULLSEND_RUNTIME and FULLSEND_PI_MANIFEST.
  • [api-contract] internal/cli/env_manifest_collector_test.go:62 — The hook-related gates (TIRITH_REQUIRED, TIRITH_FAIL_ON, FULLSEND_EGRESS_ALLOWLIST) are collected from ClaudeRuntime.Bootstrap only and published as universally reachable conditional exports. The opencode bootstrap never calls appendHookEnv, so an opencode run can satisfy the published gate without receiving the variable.
    Remediation: State the runtime prerequisite in these gates, or model hook exports per runtime and exercise hook emission across all represented runtimes.
  • [test-inadequate] internal/cli/env_manifest_collector_test.go:72 — The parser matches any NAME= token, not exported variables. Removing export from an emitter line such as export FULLSEND_ROLE=... leaves the collected name and the golden output unchanged. The regex also matches assignment-like text inside quoted values. The golden test therefore does not establish the "exported to the agent process" invariant it claims.
    Remediation: Derive exported names with the production env-data parser or an export-aware equivalent. Add negative tests for unexported assignments and assignment-like text inside values.
  • [under-engineering] internal/cli/env_manifest_collector_test.go:402 — The "keep in sync with bootstrapEnv exports" note on reservedSandboxKeys in run.go is still unenforced. Nothing asserts that every FULLSEND_* key emitted by bootstrapEnv is in reservedSandboxKeys, and go test -update regenerates the manifest without checking it. Every current key is reserved, so this is a missing safeguard against future drift, not a present defect. The linked issue's triage proposes a test for exactly this.
    Remediation: Add a test (for example TestBootstrapFullsendKeysAreReserved) asserting that every FULLSEND_* key emitted by bootstrapEnv is in reservedSandboxKeys.
  • [architectural-conflict] docs/ADRs/0146-sandbox-environment-contract-manifest.md:1 — This PR bundles ADR 0146 with a new package, roughly 1,000 lines of tests, and a generated normative artifact. docs/contributing/adrs.md and skills/writing-adrs/SKILL.md recommend keeping implementation out of ADR PRs unless it is short and trivial. The policy is worded as a recommendation, so this is advisory.
    Remediation: Consider splitting into an ADR and living-docs PR and a follow-up implementation PR, or have a maintainer explicitly accept the bundling.

Low

  • [incorrect-doc] docs/normative/sandbox-env/v1/README.md:101 — The README says a runtime that exports nothing appears with an empty set, citing opencode. opencode exports OPENCODE_CONFIG_DIR, OPENCODE_DISABLE_EXTERNAL_SKILLS and OPENCODE_DISABLE_PROJECT_CONFIG, per sandbox-env.json and the collector test. The comment on productionSandboxRuntimes in the collector makes the same wrong claim.
    Remediation: Remove opencode as the empty-set example, or describe the empty-set representation without citing a runtime that has exports.
  • [incorrect-doc] docs/contributing/runtime-implementation.md:67 — The text says registering a runtime without a manifest decision fails the golden test. Registration actually fails TestEnvManifestRuntimeEnumeration, and the runtime must be added to productionSandboxRuntimes or excludedSandboxRuntimes before the golden manifest is regenerated. The PR's own README describes this correctly, so the two docs contradict each other.
    Remediation: Name TestEnvManifestRuntimeEnumeration and the required productionSandboxRuntimes / excludedSandboxRuntimes edit.
  • [pattern-inconsistency] docs/contributing/runtime-implementation.md:64 — The new checklist entry uses - [ ], while the other items in the walkthrough use - [x].
    Remediation: Make the marker match the surrounding checklist items (- [x]).
  • [doc-style] docs/normative/sandbox-env/v1/README.md:121 — The line is 105 characters long, over the ~80-column prose wrapping rule in docs/contributing/documentation.md.
    Remediation: Rewrap the paragraph to about 80 columns.
  • [design-smell] internal/cli/env_manifest_collector_test.go:297 — envManifestRegistryRuntimes reads internal/runtime/registry.go from disk and regex-scrapes the Resolve switch labels. It fails loudly on most changes, but it couples the test to source layout. This is advisory.
    Remediation: Consider exporting a registered-runtimes helper from internal/runtime and querying it instead.
  • [complexity-ratio] internal/cli/env_manifest_collector_test.go:565 — The collector uses a mock openshell on PATH, temp-file interception, shell-token regexes and a hand-kept denylist. Its concrete weak points are covered by the findings above, so this is an advisory design note.
    Remediation: Consider sharing structured contract declarations between emitters and the manifest builder.
  • [over-engineering] internal/cli/env_manifest_collector_test.go:88 — TestEnvManifestCollector hardcodes the full manifest inventory, including all reserved keys, which TestEnvManifestGolden already byte-compares. This duplicates maintenance. The hardcoded assertions do survive -update, so the evidence is mixed. This is advisory.
    Remediation: Consider asserting structural invariants in the collector test and leaving content comparison to the golden test.

Earlier findings

  • docs/runtimes/codex.md, incomplete-doc — reclassified (still absent: not updated with a cross-reference to the sandbox-env contract; docs-only, below the posting threshold).
  • docs/runtimes/opencode.md, incomplete-doc — reclassified (same as codex.md).
  • docs/normative/sandbox-env/v1/README.md:208, backward-incompatible — reclassified (versioning table still does not classify removals or category moves; advisory).
  • docs/normative/sandbox-env/v1/README.md:126, missing-deprecation — reclassified (no deprecation notice window is defined; advisory).
  • docs/problems/agent-infrastructure.md:124, doc-style — reclassified (the edited bullet follows the section's existing single-line convention; advisory).
  • internal/envmanifest/builder.go:76, api-shape — reclassified (Build() returns a value while Parse() returns a pointer; stylistic).

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

<!-- fullsend:review-findings-v2:eyJ2ZXJzaW9uIjoyLCJhY3Rpb24iOiJyZXF1ZXN0LWNoYW5nZXMiLCJmaW5kaW5ncyI6W3sic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImxvZ2ljLWVycm9yIiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMmU4MGE3ZTdlNjE0ZGU2MSIsImxpbmUiOjM5NSwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6Im1lZGl1bSIsImNhdGVnb3J5IjoiYXBpLWNvbnRyYWN0IiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMWJiZDI3MmFiNTFiODRmMyIsImxpbmUiOjYyLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibWVkaXVtIiwiY2F0ZWdvcnkiOiJ0ZXN0LWluYWRlcXVhdGUiLCJmaWxlIjoiaW50ZXJuYWwvY2xpL2Vudl9tYW5pZmVzdF9jb2xsZWN0b3JfdGVzdC5nbyIsImlkIjoiZl83Y2ZjMzM5NTIyNWQyMjQwIiwibGluZSI6NzIsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6ImluY29ycmVjdC1kb2MiLCJmaWxlIjoiZG9jcy9ub3JtYXRpdmUvc2FuZGJveC1lbnYvdjEvUkVBRE1FLm1kIiwiaWQiOiJmXzljZTE0ZmE2ZjE3NjI5ZDMiLCJsaW5lIjoxMDEsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6ImluY29ycmVjdC1kb2MiLCJmaWxlIjoiZG9jcy9jb250cmlidXRpbmcvcnVudGltZS1pbXBsZW1lbnRhdGlvbi5tZCIsImlkIjoiZl8xMDRiMjhjMmExODMzZjMzIiwibGluZSI6NjgsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6InBhdHRlcm4taW5jb25zaXN0ZW5jeSIsImZpbGUiOiJkb2NzL2NvbnRyaWJ1dGluZy9ydW50aW1lLWltcGxlbWVudGF0aW9uLm1kIiwiaWQiOiJmXzBjZTg0N2QwNWQ5NjExOTIiLCJsaW5lIjo2NCwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6ImxvdyIsImNhdGVnb3J5IjoiZG9jLXN0eWxlIiwiZmlsZSI6ImRvY3Mvbm9ybWF0aXZlL3NhbmRib3gtZW52L3YxL1JFQURNRS5tZCIsImlkIjoiZl84OTg0MjNhYTVlOGNlNWRlIiwibGluZSI6MTIxLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoiaW5mbyIsImNhdGVnb3J5IjoiaW5jb21wbGV0ZS1kb2MiLCJmaWxlIjoiZG9jcy9ydW50aW1lcy9jb2RleC5tZCIsImlkIjoiZl84ZTFhM2M3YTlkOTIzNTliIiwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJpbmZvIiwiY2F0ZWdvcnkiOiJpbmNvbXBsZXRlLWRvYyIsImZpbGUiOiJkb2NzL3J1bnRpbWVzL29wZW5jb2RlLm1kIiwiaWQiOiJmXzFmZTI1MjEwOGJmYzFhYjkiLCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6ImluZm8iLCJjYXRlZ29yeSI6ImJhY2t3YXJkLWluY29tcGF0aWJsZSIsImZpbGUiOiJkb2NzL25vcm1hdGl2ZS9zYW5kYm94LWVudi92MS9SRUFETUUubWQiLCJpZCI6ImZfNGYwYjZlNzViMTkxMTAwYiIsImxpbmUiOjIwOCwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJpbmZvIiwiY2F0ZWdvcnkiOiJtaXNzaW5nLWRlcHJlY2F0aW9uIiwiZmlsZSI6ImRvY3Mvbm9ybWF0aXZlL3NhbmRib3gtZW52L3YxL1JFQURNRS5tZCIsImlkIjoiZl85OGViNWU2MzNjNDhkNzQ2IiwibGluZSI6MTI2LCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6ImluZm8iLCJjYXRlZ29yeSI6ImRvYy1zdHlsZSIsImZpbGUiOiJkb2NzL3Byb2JsZW1zL2FnZW50LWluZnJhc3RydWN0dXJlLm1kIiwiaWQiOiJmXzE4NDliZDU4MjViMzVmYjkiLCJsaW5lIjoxMjQsImFjdGlvbmFibGUiOmZhbHNlfSx7InNldmVyaXR5IjoiaW5mbyIsImNhdGVnb3J5IjoiYXBpLXNoYXBlIiwiZmlsZSI6ImludGVybmFsL2Vudm1hbmlmZXN0L2J1aWxkZXIuZ28iLCJpZCI6ImZfYWZjNDdkYTA1MWRiYjhiZiIsImxpbmUiOjc2LCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6Im1lZGl1bSIsImNhdGVnb3J5IjoiYXBpLWNvbnRyYWN0IiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfNjEwZDk4NmVkMmVlYTdiOCIsImxpbmUiOjYyLCJhY3Rpb25hYmxlIjp0cnVlfV0sImRpc3Bvc2l0aW9ucyI6W3siaWQiOiJmXzJlODBhN2U3ZTYxNGRlNjEiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfMWJiZDI3MmFiNTFiODRmMyIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl83Y2ZjMzM5NTIyNWQyMjQwIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzljZTE0ZmE2ZjE3NjI5ZDMiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfMTA0YjI4YzJhMTgzM2YzMyIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl8wY2U4NDdkMDVkOTYxMTkyIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzg5ODQyM2FhNWU4Y2U1ZGUiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfOGUxYTNjN2E5ZDkyMzU5YiIsInN0YXR1cyI6InJlY2xhc3NpZmllZCJ9LHsiaWQiOiJmXzFmZTI1MjEwOGJmYzFhYjkiLCJzdGF0dXMiOiJyZWNsYXNzaWZpZWQifSx7ImlkIjoiZl80ZjBiNmU3NWIxOTExMDBiIiwic3RhdHVzIjoicmVjbGFzc2lmaWVkIn0seyJpZCI6ImZfOThlYjVlNjMzYzQ4ZDc0NiIsInN0YXR1cyI6InJlY2xhc3NpZmllZCJ9LHsiaWQiOiJmXzE4NDliZDU4MjViMzVmYjkiLCJzdGF0dXMiOiJyZWNsYXNzaWZpZWQifSx7ImlkIjoiZl9hZmM0N2RhMDUxZGJiOGJmIiwic3RhdHVzIjoicmVjbGFzc2lmaWVkIn0seyJpZCI6ImZfNjEwZDk4NmVkMmVlYTdiOCIsInN0YXR1cyI6InJlc29sdmVkX2J5X2NoYW5nZSJ9XX0= -->

Review

Findings

Medium

  • [logic-error] internal/cli/env_manifest_collector_test.go:395 — Runtime collection reads only EnvExports(), so it misses variables the production launch commands export: FULLSEND_RUNTIME, FULLSEND_PI_MANIFEST and FULLSEND_PI_HOOK_PYTHON (pi_run.go), FULLSEND_RUNTIME and RUST_LOG (codex_run.go), FULLSEND_RUNTIME (opencode_run.go). None is in sandbox-env.json. A downstream reachability validator would reject variables that really are available, and changes to those emitters never reach the golden test.
    Remediation: Collect exports from each runtime's production launch construction, including the relevant conditional branches, and regenerate the manifest with accurate runtime and gate classifications.
  • [api-contract] internal/cli/env_manifest_collector_test.go:62 — The hook-variable gates (TIRITH_REQUIRED, TIRITH_FAIL_ON, FULLSEND_EGRESS_ALLOWLIST) are derived from ClaudeRuntime.Bootstrap alone, and the README says conditional exports are reachable under any runtime. appendHookEnv is called only from the claude, codex and pi bootstraps, never OpenCode's, so under opencode these variables are not emitted even when the gate holds.
    Remediation: Add the supported-runtime restriction to these gates and test hook-variable availability across every enumerated production runtime.
  • [test-inadequate] internal/cli/env_manifest_collector_test.go:72 — The export parser regex matches any KEY= assignment, not exported variables. Changing export CLAUDE_CONFIG_DIR=... to CLAUDE_CONFIG_DIR=... leaves the collector output and golden test unchanged even though the child process no longer receives the variable. The hardcoded readonlyNotExported list patches only the current false positives.
    Remediation: Derive exported names from an isolated execution environment, or parse export declarations explicitly, and add a regression test showing that removing export changes the derived contract.

Low

  • [incorrect-doc] docs/normative/sandbox-env/v1/README.md:101 — States that a runtime exporting nothing appears with an empty set, citing opencode. But sandbox-env.json lists three opencode exports (OPENCODE_CONFIG_DIR, OPENCODE_DISABLE_EXTERNAL_SKILLS, OPENCODE_DISABLE_PROJECT_CONFIG).
    Remediation: Remove the opencode example or replace it with a runtime that actually exports nothing.
  • [incorrect-doc] docs/contributing/runtime-implementation.md:68 — Says registering a runtime without a manifest decision fails the golden test (TestEnvManifestGolden). The failure comes from TestEnvManifestRuntimeEnumeration; the golden test iterates a hardcoded runtime list and never reads the registry.
    Remediation: Name TestEnvManifestRuntimeEnumeration as the failing test.
  • [pattern-inconsistency] docs/contributing/runtime-implementation.md:64 — The new checklist item uses - [ ] while sibling touchpoint items use - [x].
    Remediation: Change - [ ] to - [x].
  • [doc-style] docs/normative/sandbox-env/v1/README.md:121 — Line is about 106 characters, exceeding the ~80-column Markdown wrapping convention in docs/contributing/documentation.md.
    Remediation: Rewrap the sentence near 80 columns.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

<!-- fullsend:review-findings-v2:eyJ2ZXJzaW9uIjoyLCJhY3Rpb24iOiJyZXF1ZXN0LWNoYW5nZXMiLCJmaW5kaW5ncyI6W3sic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImxvZ2ljLWVycm9yIiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMmU4MGE3ZTdlNjE0ZGU2MSIsImxpbmUiOjM5NSwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6Im1lZGl1bSIsImNhdGVnb3J5IjoiYXBpLWNvbnRyYWN0IiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMWJiZDI3MmFiNTFiODRmMyIsImxpbmUiOjYyLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibWVkaXVtIiwiY2F0ZWdvcnkiOiJ0ZXN0LWluYWRlcXVhdGUiLCJmaWxlIjoiaW50ZXJuYWwvY2xpL2Vudl9tYW5pZmVzdF9jb2xsZWN0b3JfdGVzdC5nbyIsImlkIjoiZl83Y2ZjMzM5NTIyNWQyMjQwIiwibGluZSI6NzIsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6ImluY29ycmVjdC1kb2MiLCJmaWxlIjoiZG9jcy9ub3JtYXRpdmUvc2FuZGJveC1lbnYvdjEvUkVBRE1FLm1kIiwiaWQiOiJmXzljZTE0ZmE2ZjE3NjI5ZDMiLCJsaW5lIjoxMDAsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6ImluY29ycmVjdC1kb2MiLCJmaWxlIjoiZG9jcy9jb250cmlidXRpbmcvcnVudGltZS1pbXBsZW1lbnRhdGlvbi5tZCIsImlkIjoiZl8xMDRiMjhjMmExODMzZjMzIiwibGluZSI6NjgsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6InBhdHRlcm4taW5jb25zaXN0ZW5jeSIsImZpbGUiOiJkb2NzL2NvbnRyaWJ1dGluZy9ydW50aW1lLWltcGxlbWVudGF0aW9uLm1kIiwiaWQiOiJmXzBjZTg0N2QwNWQ5NjExOTIiLCJsaW5lIjo2NCwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6ImxvdyIsImNhdGVnb3J5IjoiZG9jLXN0eWxlIiwiZmlsZSI6ImRvY3Mvbm9ybWF0aXZlL3NhbmRib3gtZW52L3YxL1JFQURNRS5tZCIsImlkIjoiZl84OTg0MjNhYTVlOGNlNWRlIiwibGluZSI6MTIxLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoiaW5mbyIsImNhdGVnb3J5IjoiaW5jb21wbGV0ZS1kb2MiLCJmaWxlIjoiZG9jcy9ydW50aW1lcy9jb2RleC5tZCIsImlkIjoiZl84ZTFhM2M3YTlkOTIzNTliIiwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJpbmZvIiwiY2F0ZWdvcnkiOiJpbmNvbXBsZXRlLWRvYyIsImZpbGUiOiJkb2NzL3J1bnRpbWVzL29wZW5jb2RlLm1kIiwiaWQiOiJmXzFmZTI1MjEwOGJmYzFhYjkiLCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6ImluZm8iLCJjYXRlZ29yeSI6ImJhY2t3YXJkLWluY29tcGF0aWJsZSIsImZpbGUiOiJkb2NzL25vcm1hdGl2ZS9zYW5kYm94LWVudi92MS9SRUFETUUubWQiLCJpZCI6ImZfNGYwYjZlNzViMTkxMTAwYiIsImxpbmUiOjIwOCwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJpbmZvIiwiY2F0ZWdvcnkiOiJtaXNzaW5nLWRlcHJlY2F0aW9uIiwiZmlsZSI6ImRvY3Mvbm9ybWF0aXZlL3NhbmRib3gtZW52L3YxL1JFQURNRS5tZCIsImlkIjoiZl85OGViNWU2MzNjNDhkNzQ2IiwibGluZSI6MTI2LCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6ImluZm8iLCJjYXRlZ29yeSI6ImRvYy1zdHlsZSIsImZpbGUiOiJkb2NzL3Byb2JsZW1zL2FnZW50LWluZnJhc3RydWN0dXJlLm1kIiwiaWQiOiJmXzE4NDliZDU4MjViMzVmYjkiLCJsaW5lIjoxMjQsImFjdGlvbmFibGUiOmZhbHNlfSx7InNldmVyaXR5IjoiaW5mbyIsImNhdGVnb3J5IjoiYXBpLXNoYXBlIiwiZmlsZSI6ImludGVybmFsL2Vudm1hbmlmZXN0L2J1aWxkZXIuZ28iLCJpZCI6ImZfYWZjNDdkYTA1MWRiYjhiZiIsImxpbmUiOjc2LCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6Im1lZGl1bSIsImNhdGVnb3J5IjoiYXBpLWNvbnRyYWN0IiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfNjEwZDk4NmVkMmVlYTdiOCIsImxpbmUiOjYyLCJhY3Rpb25hYmxlIjp0cnVlfV0sImRpc3Bvc2l0aW9ucyI6W3siaWQiOiJmXzJlODBhN2U3ZTYxNGRlNjEiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfMWJiZDI3MmFiNTFiODRmMyIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl83Y2ZjMzM5NTIyNWQyMjQwIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzljZTE0ZmE2ZjE3NjI5ZDMiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfMTA0YjI4YzJhMTgzM2YzMyIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl8wY2U4NDdkMDVkOTYxMTkyIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzg5ODQyM2FhNWU4Y2U1ZGUiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfOGUxYTNjN2E5ZDkyMzU5YiIsInN0YXR1cyI6InJlY2xhc3NpZmllZCJ9LHsiaWQiOiJmXzFmZTI1MjEwOGJmYzFhYjkiLCJzdGF0dXMiOiJyZWNsYXNzaWZpZWQifSx7ImlkIjoiZl80ZjBiNmU3NWIxOTExMDBiIiwic3RhdHVzIjoicmVjbGFzc2lmaWVkIn0seyJpZCI6ImZfOThlYjVlNjMzYzQ4ZDc0NiIsInN0YXR1cyI6InJlY2xhc3NpZmllZCJ9LHsiaWQiOiJmXzE4NDliZDU4MjViMzVmYjkiLCJzdGF0dXMiOiJyZWNsYXNzaWZpZWQifSx7ImlkIjoiZl9hZmM0N2RhMDUxZGJiOGJmIiwic3RhdHVzIjoicmVjbGFzc2lmaWVkIn0seyJpZCI6ImZfNjEwZDk4NmVkMmVlYTdiOCIsInN0YXR1cyI6InJlc29sdmVkX2J5X2NoYW5nZSJ9XX0= -->

Review

Findings

Medium

  • [logic-error] internal/cli/env_manifest_collector_test.go:395 — The collector reads only EnvExports(), so variables exported by runtime launch commands (e.g. FULLSEND_RUNTIME and FULLSEND_PI_MANIFEST in pi_run.go, FULLSEND_RUNTIME in codex_run.go/opencode_run.go) are absent from the manifest, and changes to those emitters pass the golden check without changing the published contract.
    Remediation: Collect the runtime launch-time exports as well as EnvExports(), exercising conditional launch paths and representing their runtime restrictions and gates.
  • [api-contract] internal/cli/env_manifest_collector_test.go:62 — The hook-variable gates (TIRITH_REQUIRED, TIRITH_FAIL_ON, FULLSEND_EGRESS_ALLOWLIST) omit the runtime prerequisite. OpenCodeRuntime.Bootstrap installs no hooks and never calls appendHookEnv, so an opencode run can satisfy the published gate without receiving them.
    Remediation: Include hook-support/runtime prerequisites in these gate descriptions, or represent hook exports under runtime-qualified conditional categories.
  • [test-inadequate] internal/cli/env_manifest_collector_test.go:72 — The export collector matches any NAME= token rather than checking that the name is exported. Changing export PI_OFFLINE=1 to PI_OFFLINE=1 yields identical collector output although the agent no longer inherits the variable, and NAME= text inside quoted values can be mistaken for variables.
    Remediation: Use export-aware parsing or controlled execution that inspects the child environment, and add regression cases for non-exported assignments and quoted NAME= text.

Low

  • [incorrect-doc] docs/normative/sandbox-env/v1/README.md:100 — The README says a runtime that exports nothing appears with an empty set, citing opencode. sandbox-env.json lists three opencode variables (OPENCODE_CONFIG_DIR, OPENCODE_DISABLE_EXTERNAL_SKILLS, OPENCODE_DISABLE_PROJECT_CONFIG), and no registered runtime has an empty set. The same stale "empty export set" wording is in the comment at internal/cli/env_manifest_collector_test.go:41.
    Remediation: Remove or reword the opencode example in the README and the collector comment so they match sandbox-env.json.
  • [incorrect-doc] docs/contributing/runtime-implementation.md:68 — The checklist says registering a runtime without a manifest decision fails the golden test. It actually fails TestEnvManifestRuntimeEnumeration, and -update does not help until the runtime is classified in productionSandboxRuntimes or excludedSandboxRuntimes.
    Remediation: Name TestEnvManifestRuntimeEnumeration and tell the developer to classify the runtime before regenerating the golden manifest.
  • [pattern-inconsistency] docs/contributing/runtime-implementation.md:64 — The new touchpoint uses - [ ], while every other item in the checklist uses - [x].
    Remediation: Change it to - [x].
  • [doc-style] docs/normative/sandbox-env/v1/README.md:121 — A prose line of about 105 columns breaks the ~80-column wrapping rule in docs/contributing/documentation.md.
    Remediation: Wrap the sentence close to 80 columns.

Earlier findings

  • docs/runtimes/codex.md, incomplete-doc, reclassified to info — codex.md still has no pointer to the sandbox environment contract (not touched by this PR).
  • docs/runtimes/opencode.md, incomplete-doc, reclassified to info — opencode.md still has no pointer to the contract (not touched by this PR).
  • docs/normative/sandbox-env/v1/README.md:208, backward-incompatible, reclassified to info — the Versioning table is still silent on removal of guaranteed variables and on newly reserved keys.
  • docs/normative/sandbox-env/v1/README.md:126, missing-deprecation, reclassified to info — the deprecation notice period is still undefined.
  • docs/problems/agent-infrastructure.md:124, doc-style, reclassified to info — the very long open-question line is unchanged.
  • internal/envmanifest/builder.go:76, api-shape, reclassified to info — Build() is unchanged.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (4)

<!-- fullsend:review-findings-v2:eyJ2ZXJzaW9uIjoyLCJhY3Rpb24iOiJyZXF1ZXN0LWNoYW5nZXMiLCJmaW5kaW5ncyI6W3sic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImxvZ2ljLWVycm9yIiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMmU4MGE3ZTdlNjE0ZGU2MSIsImxpbmUiOjM5NSwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6Im1lZGl1bSIsImNhdGVnb3J5IjoiYXBpLWNvbnRyYWN0IiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMWJiZDI3MmFiNTFiODRmMyIsImxpbmUiOjYyLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibWVkaXVtIiwiY2F0ZWdvcnkiOiJ0ZXN0LWluYWRlcXVhdGUiLCJmaWxlIjoiaW50ZXJuYWwvY2xpL2Vudl9tYW5pZmVzdF9jb2xsZWN0b3JfdGVzdC5nbyIsImlkIjoiZl83Y2ZjMzM5NTIyNWQyMjQwIiwibGluZSI6NzIsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6ImluY29ycmVjdC1kb2MiLCJmaWxlIjoiZG9jcy9ub3JtYXRpdmUvc2FuZGJveC1lbnYvdjEvUkVBRE1FLm1kIiwiaWQiOiJmXzljZTE0ZmE2ZjE3NjI5ZDMiLCJsaW5lIjoxMDAsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6ImluY29ycmVjdC1kb2MiLCJmaWxlIjoiZG9jcy9jb250cmlidXRpbmcvcnVudGltZS1pbXBsZW1lbnRhdGlvbi5tZCIsImlkIjoiZl8xMDRiMjhjMmExODMzZjMzIiwibGluZSI6NjgsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6InBhdHRlcm4taW5jb25zaXN0ZW5jeSIsImZpbGUiOiJkb2NzL2NvbnRyaWJ1dGluZy9ydW50aW1lLWltcGxlbWVudGF0aW9uLm1kIiwiaWQiOiJmXzBjZTg0N2QwNWQ5NjExOTIiLCJsaW5lIjo2NCwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6ImxvdyIsImNhdGVnb3J5IjoiZG9jLXN0eWxlIiwiZmlsZSI6ImRvY3Mvbm9ybWF0aXZlL3NhbmRib3gtZW52L3YxL1JFQURNRS5tZCIsImlkIjoiZl84OTg0MjNhYTVlOGNlNWRlIiwibGluZSI6MTIxLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoiaW5mbyIsImNhdGVnb3J5IjoiaW5jb21wbGV0ZS1kb2MiLCJmaWxlIjoiZG9jcy9ydW50aW1lcy9jb2RleC5tZCIsImlkIjoiZl84ZTFhM2M3YTlkOTIzNTliIiwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJpbmZvIiwiY2F0ZWdvcnkiOiJpbmNvbXBsZXRlLWRvYyIsImZpbGUiOiJkb2NzL3J1bnRpbWVzL29wZW5jb2RlLm1kIiwiaWQiOiJmXzFmZTI1MjEwOGJmYzFhYjkiLCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6ImluZm8iLCJjYXRlZ29yeSI6ImJhY2t3YXJkLWluY29tcGF0aWJsZSIsImZpbGUiOiJkb2NzL25vcm1hdGl2ZS9zYW5kYm94LWVudi92MS9SRUFETUUubWQiLCJpZCI6ImZfNGYwYjZlNzViMTkxMTAwYiIsImxpbmUiOjIwOCwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJpbmZvIiwiY2F0ZWdvcnkiOiJtaXNzaW5nLWRlcHJlY2F0aW9uIiwiZmlsZSI6ImRvY3Mvbm9ybWF0aXZlL3NhbmRib3gtZW52L3YxL1JFQURNRS5tZCIsImlkIjoiZl85OGViNWU2MzNjNDhkNzQ2IiwibGluZSI6MTI2LCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6ImluZm8iLCJjYXRlZ29yeSI6ImRvYy1zdHlsZSIsImZpbGUiOiJkb2NzL3Byb2JsZW1zL2FnZW50LWluZnJhc3RydWN0dXJlLm1kIiwiaWQiOiJmXzE4NDliZDU4MjViMzVmYjkiLCJsaW5lIjoxMjQsImFjdGlvbmFibGUiOmZhbHNlfSx7InNldmVyaXR5IjoiaW5mbyIsImNhdGVnb3J5IjoiYXBpLXNoYXBlIiwiZmlsZSI6ImludGVybmFsL2Vudm1hbmlmZXN0L2J1aWxkZXIuZ28iLCJpZCI6ImZfYWZjNDdkYTA1MWRiYjhiZiIsImxpbmUiOjc2LCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6Im1lZGl1bSIsImNhdGVnb3J5IjoiYXBpLWNvbnRyYWN0IiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfNjEwZDk4NmVkMmVlYTdiOCIsImxpbmUiOjYyLCJhY3Rpb25hYmxlIjp0cnVlfV0sImRpc3Bvc2l0aW9ucyI6W3siaWQiOiJmXzJlODBhN2U3ZTYxNGRlNjEiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfMWJiZDI3MmFiNTFiODRmMyIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl83Y2ZjMzM5NTIyNWQyMjQwIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzg5ODQyM2FhNWU4Y2U1ZGUiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfMGNlODQ3ZDA1ZDk2MTE5MiIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl8xMDRiMjhjMmExODMzZjMzIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzljZTE0ZmE2ZjE3NjI5ZDMiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfOGUxYTNjN2E5ZDkyMzU5YiIsInN0YXR1cyI6InJlY2xhc3NpZmllZCJ9LHsiaWQiOiJmXzFmZTI1MjEwOGJmYzFhYjkiLCJzdGF0dXMiOiJyZWNsYXNzaWZpZWQifSx7ImlkIjoiZl80ZjBiNmU3NWIxOTExMDBiIiwic3RhdHVzIjoicmVjbGFzc2lmaWVkIn0seyJpZCI6ImZfOThlYjVlNjMzYzQ4ZDc0NiIsInN0YXR1cyI6InJlY2xhc3NpZmllZCJ9LHsiaWQiOiJmXzE4NDliZDU4MjViMzVmYjkiLCJzdGF0dXMiOiJyZWNsYXNzaWZpZWQifSx7ImlkIjoiZl9hZmM0N2RhMDUxZGJiOGJmIiwic3RhdHVzIjoicmVjbGFzc2lmaWVkIn0seyJpZCI6ImZfNjEwZDk4NmVkMmVlYTdiOCIsInN0YXR1cyI6InJlc29sdmVkX2J5X2NoYW5nZSJ9XX0= -->

Review

Findings

Medium

  • [logic-error] internal/cli/env_manifest_collector_test.go:395 — Runtime collection only examines EnvExports(), omitting variables exported by runtime launch commands. internal/runtime/pi_run.go exports FULLSEND_PI_MANIFEST, FULLSEND_PI_MANIFEST_SHA256, FULLSEND_PI_HOOK_PYTHON, FULLSEND_PI_HOOK_PATH, FULLSEND_PI_CHILD_BIN and FULLSEND_PI_CHILD_NODE, but none appears in the generated manifest, and changes to those emitters leave the golden test unchanged. The published inventory is incomplete for its stated env-var reachability purpose.
    Remediation: Collect exports from runtime launch paths as well, preserving their runtime and conditional gates, and add assertions covering representative launch-only exports.
  • [api-contract] internal/cli/env_manifest_collector_test.go:62 — The hook-variable gates (TIRITH_REQUIRED, TIRITH_FAIL_ON, FULLSEND_EGRESS_ALLOWLIST) omit the runtime restriction, while the normative README says conditional exports are reachable under any runtime when their gate holds. appendHookEnv is only called from claude.go, codex_bootstrap.go and pi_bootstrap.go; OpenCodeRuntime never calls it, so an OpenCode harness can satisfy the gates without receiving these variables.
    Remediation: Include the supported-runtime restriction in these gates or represent runtime-conditioned exports explicitly, and test hook-variable behavior across every listed runtime.
  • [test-inadequate] internal/cli/env_manifest_collector_test.go:72 — envManifestKeyRe matches any KEY= token and does not establish that the variable is exported: export CLAUDE_CONFIG_DIR=... and CLAUDE_CONFIG_DIR=... collect identically, so dropping export from an emitter breaks the sandbox env while the collector and golden tests keep passing. It also picks up assignment-looking text inside quoted values. The hand-maintained readonly-not-exported exclusions do not address either general parsing gap.
    Remediation: Derive exported names with shell-aware parsing or a controlled execution probe, and add negative tests for non-exported assignments and assignment-looking text in quoted values, plus a regression test showing that removing export changes the collected contract.

Low

  • [incorrect-doc] docs/normative/sandbox-env/v1/README.md:100 — The README says a runtime that exports nothing appears with an empty set, citing opencode. OpenCodeRuntime.EnvExports() exports OPENCODE_CONFIG_DIR, OPENCODE_DISABLE_EXTERNAL_SKILLS and OPENCODE_DISABLE_PROJECT_CONFIG, and sandbox-env.json lists them, so opencode does not have an empty set. The productionSandboxRuntimes comment in the collector test repeats the stale claim.
    Remediation: Drop the opencode example (or use a runtime that really exports nothing, e.g. dummy) and fix the matching test comment.
  • [incorrect-doc] docs/contributing/runtime-implementation.md:68 — The checklist says registering a runtime without a manifest decision fails the golden test and can be fixed with -update. Registering a runtime actually fails TestEnvManifestRuntimeEnumeration until productionSandboxRuntimes or excludedSandboxRuntimes in env_manifest_collector_test.go is updated; -update alone does not fix it (the README names the correct test).
    Remediation: Name TestEnvManifestRuntimeEnumeration and say the runtime must be added to the test's enumeration before regenerating the golden manifest.
  • [pattern-inconsistency] docs/contributing/runtime-implementation.md:64 — The env manifest collector/golden checklist entry is - [ ], while every other item in the consumer-completeness touchpoints checklist is - [x].
    Remediation: Change - [ ] to - [x].
  • [doc-style] docs/normative/sandbox-env/v1/README.md:121 — Line 121 is 106 columns wide, exceeding the repository's ~80-column Markdown prose wrapping rule.
    Remediation: Rewrap the line near 80 columns.

Earlier findings

  • docs/runtimes/codex.md, incomplete-doc — reclassified (low → info): nothing stale; CODEX_HOME already documented there.
  • docs/runtimes/opencode.md, incomplete-doc — reclassified (low → info): optional cross-reference only.
  • docs/normative/sandbox-env/v1/README.md:208, backward-incompatible — reclassified (low → info): table versions document shape, not runner behavior (README lines 208-212).

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (5)

<!-- fullsend:review-findings-v2:eyJ2ZXJzaW9uIjoyLCJhY3Rpb24iOiJyZXF1ZXN0LWNoYW5nZXMiLCJmaW5kaW5ncyI6W3sic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImxvZ2ljLWVycm9yIiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMmU4MGE3ZTdlNjE0ZGU2MSIsImxpbmUiOjM5NSwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6Im1lZGl1bSIsImNhdGVnb3J5IjoiYXBpLWNvbnRyYWN0IiwiZmlsZSI6ImludGVybmFsL2NsaS9lbnZfbWFuaWZlc3RfY29sbGVjdG9yX3Rlc3QuZ28iLCJpZCI6ImZfMWJiZDI3MmFiNTFiODRmMyIsImxpbmUiOjYyLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibWVkaXVtIiwiY2F0ZWdvcnkiOiJ0ZXN0LWluYWRlcXVhdGUiLCJmaWxlIjoiaW50ZXJuYWwvY2xpL2Vudl9tYW5pZmVzdF9jb2xsZWN0b3JfdGVzdC5nbyIsImlkIjoiZl83Y2ZjMzM5NTIyNWQyMjQwIiwibGluZSI6NzIsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJsb3ciLCJjYXRlZ29yeSI6ImRvYy1zdHlsZSIsImZpbGUiOiJkb2NzL25vcm1hdGl2ZS9zYW5kYm94LWVudi92MS9SRUFETUUubWQiLCJpZCI6ImZfODk4NDIzYWE1ZThjZTVkZSIsImxpbmUiOjEyMSwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6ImxvdyIsImNhdGVnb3J5IjoicGF0dGVybi1pbmNvbnNpc3RlbmN5IiwiZmlsZSI6ImRvY3MvY29udHJpYnV0aW5nL3J1bnRpbWUtaW1wbGVtZW50YXRpb24ubWQiLCJpZCI6ImZfMGNlODQ3ZDA1ZDk2MTE5MiIsImxpbmUiOjY0LCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibG93IiwiY2F0ZWdvcnkiOiJpbmNvcnJlY3QtZG9jIiwiZmlsZSI6ImRvY3MvY29udHJpYnV0aW5nL3J1bnRpbWUtaW1wbGVtZW50YXRpb24ubWQiLCJpZCI6ImZfMTA0YjI4YzJhMTgzM2YzMyIsImxpbmUiOjY4LCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibG93IiwiY2F0ZWdvcnkiOiJpbmNvcnJlY3QtZG9jIiwiZmlsZSI6ImRvY3Mvbm9ybWF0aXZlL3NhbmRib3gtZW52L3YxL1JFQURNRS5tZCIsImlkIjoiZl85Y2UxNGZhNmYxNzYyOWQzIiwibGluZSI6MTAwLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibG93IiwiY2F0ZWdvcnkiOiJpbmNvbXBsZXRlLWRvYyIsImZpbGUiOiJkb2NzL3J1bnRpbWVzL2NvZGV4Lm1kIiwiaWQiOiJmXzhlMWEzYzdhOWQ5MjM1OWIiLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibG93IiwiY2F0ZWdvcnkiOiJpbmNvbXBsZXRlLWRvYyIsImZpbGUiOiJkb2NzL3J1bnRpbWVzL29wZW5jb2RlLm1kIiwiaWQiOiJmXzFmZTI1MjEwOGJmYzFhYjkiLCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibG93IiwiY2F0ZWdvcnkiOiJiYWNrd2FyZC1pbmNvbXBhdGlibGUiLCJmaWxlIjoiZG9jcy9ub3JtYXRpdmUvc2FuZGJveC1lbnYvdjEvUkVBRE1FLm1kIiwiaWQiOiJmXzRmMGI2ZTc1YjE5MTEwMGIiLCJsaW5lIjoyMDgsImFjdGlvbmFibGUiOmZhbHNlfSx7InNldmVyaXR5IjoiaW5mbyIsImNhdGVnb3J5IjoibWlzc2luZy1kZXByZWNhdGlvbiIsImZpbGUiOiJkb2NzL25vcm1hdGl2ZS9zYW5kYm94LWVudi92MS9SRUFETUUubWQiLCJpZCI6ImZfOThlYjVlNjMzYzQ4ZDc0NiIsImxpbmUiOjEyNiwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJpbmZvIiwiY2F0ZWdvcnkiOiJkb2Mtc3R5bGUiLCJmaWxlIjoiZG9jcy9wcm9ibGVtcy9hZ2VudC1pbmZyYXN0cnVjdHVyZS5tZCIsImlkIjoiZl8xODQ5YmQ1ODI1YjM1ZmI5IiwibGluZSI6MTI0LCJhY3Rpb25hYmxlIjpmYWxzZX0seyJzZXZlcml0eSI6ImluZm8iLCJjYXRlZ29yeSI6ImFwaS1zaGFwZSIsImZpbGUiOiJpbnRlcm5hbC9lbnZtYW5pZmVzdC9idWlsZGVyLmdvIiwiaWQiOiJmX2FmYzQ3ZGEwNTFkYmI4YmYiLCJsaW5lIjo3NiwiYWN0aW9uYWJsZSI6ZmFsc2V9LHsic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImFwaS1jb250cmFjdCIsImZpbGUiOiJpbnRlcm5hbC9jbGkvZW52X21hbmlmZXN0X2NvbGxlY3Rvcl90ZXN0LmdvIiwiaWQiOiJmXzYxMGQ5ODZlZDJlZWE3YjgiLCJsaW5lIjo2MiwiYWN0aW9uYWJsZSI6dHJ1ZX1dLCJkaXNwb3NpdGlvbnMiOlt7ImlkIjoiZl8yZTgwYTdlN2U2MTRkZTYxIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzFiYmQyNzJhYjUxYjg0ZjMiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfN2NmYzMzOTUyMjVkMjI0MCIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl84OTg0MjNhYTVlOGNlNWRlIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzBjZTg0N2QwNWQ5NjExOTIiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfMTA0YjI4YzJhMTgzM2YzMyIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl84ZTFhM2M3YTlkOTIzNTliIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzFmZTI1MjEwOGJmYzFhYjkiLCJzdGF0dXMiOiJvcGVuIn0seyJpZCI6ImZfOWNlMTRmYTZmMTc2MjlkMyIsInN0YXR1cyI6Im9wZW4ifSx7ImlkIjoiZl80ZjBiNmU3NWIxOTExMDBiIiwic3RhdHVzIjoib3BlbiJ9LHsiaWQiOiJmXzk4ZWI1ZTYzM2M0OGQ3NDYiLCJzdGF0dXMiOiJyZWNsYXNzaWZpZWQifSx7ImlkIjoiZl8xODQ5YmQ1ODI1YjM1ZmI5Iiwic3RhdHVzIjoicmVjbGFzc2lmaWVkIn0seyJpZCI6ImZfYWZjNDdkYTA1MWRiYjhiZiIsInN0YXR1cyI6InJlY2xhc3NpZmllZCJ9LHsiaWQiOiJmXzYxMGQ5ODZlZDJlZWE3YjgiLCJzdGF0dXMiOiJyZXNvbHZlZF9ieV9jaGFuZ2UifV19 -->

Review

Findings

Medium

  • [logic-error] internal/cli/env_manifest_collector_test.go:395 — The runtime inventory only collects EnvExports(), so it misses variables that runtime launch builders inject. internal/runtime/pi_run.go exports FULLSEND_RUNTIME and FULLSEND_PI_MANIFEST/FULLSEND_PI_MANIFEST_SHA256 on its own, outside EnvExports(). codex_run.go and opencode_run.go also define FULLSEND_RUNTIME launch exports. None of these appears in sandbox-env.json. A skill that reads FULLSEND_RUNTIME could therefore be rejected as unreachable even though the runner supplies it, and changing these launch-time exports does not change the golden output.
    Remediation: Also collect the production launch-time exports (for example from each runtime's launch command builder), not just bootstrap exports and EnvExports(). Exercise their provider and hook gates, represent unconditional and gated runtime exports correctly, then regenerate the manifest.

  • [api-contract] internal/cli/env_manifest_collector_test.go:62 — The hook variables (TIRITH_FAIL_ON, TIRITH_REQUIRED, FULLSEND_EGRESS_ALLOWLIST) are published as conditional exports reachable under every runtime, but their gate descriptions leave out runtime support. hookEnvExportKeys derives them only through ClaudeRuntime.Bootstrap. appendHookEnv is called only from claude.go, codex_bootstrap.go and pi_bootstrap.go, never from OpenCode, so an OpenCode sandbox does not get TIRITH_REQUIRED even though its documented gate says it defaults to true. Consumers are given a false portability guarantee.
    Remediation: Add runtime hook support to these gate descriptions, or model these exports as runtime-dependent conditional variables. Exercise each production runtime when validating the classification.

  • [test-inadequate] internal/cli/env_manifest_collector_test.go:72 — envManifestKeyRe matches any assignment-shaped substring, not just exported environment variables. parseExportedEnvKeys returns the same key for export X=..., X=..., and a commented-out # export X=..., and can pull a key out of assignment-like text inside a quoted value. Removing or commenting out an export can therefore leave the golden manifest unchanged while the agent loses the variable. The hand-maintained readonlyNotExported exclusion list exists because non-exported assignments were already being picked up.
    Remediation: Parse the emitted environment with export-aware syntax, or observe the resulting child-process environment. Add parser regression tests for non-exported assignments, comments, assignment-like text inside values, and separate export declarations.

Low

  • [doc-style] docs/normative/sandbox-env/v1/README.md:121 — This line holds the end of one sentence and the start of another, and exceeds the repository's ~80-column wrapping convention for Markdown prose.
    Remediation: Wrap prose at about 80 columns by breaking the line after 'variables (e.g. HTTPS_PROXY, LD_PRELOAD, NODE_OPTIONS).'.

  • [pattern-inconsistency] docs/contributing/runtime-implementation.md:64 — The new checklist item is unchecked (- [ ]), while every other checklist entry in the file uses - [x]. See also: [incorrect-doc] finding at this location.
    Remediation: Change - [ ] to - [x] to match the other items in this walkthrough.

  • [incorrect-doc] docs/contributing/runtime-implementation.md:68 — The checklist says that registering a runtime without a manifest decision fails the golden test. TestEnvManifestRuntimeEnumeration in env_manifest_collector_test.go is the test that pins productionSandboxRuntimes to the registry; TestEnvManifestGolden only compares the committed JSON byte-for-byte with collector output. This also contradicts the sandbox-env README, which correctly names TestEnvManifestRuntimeEnumeration. See also: [pattern-inconsistency] finding at this location.
    Remediation: Reword the item to say that TestEnvManifestRuntimeEnumeration fails when a runtime is registered without a manifest decision.

  • [incorrect-doc] docs/normative/sandbox-env/v1/README.md:100 — Lines 100-101 say: 'A runtime that exports nothing appears with an empty set — opencode — rather than being omitted.' In sandbox-env.json, opencode exports OPENCODE_CONFIG_DIR, OPENCODE_DISABLE_EXTERNAL_SKILLS and OPENCODE_DISABLE_PROJECT_CONFIG, so it does not appear with an empty set.
    Remediation: Remove opencode as the example of an empty set, or name a runtime that actually has an empty set, if one exists.

  • [incomplete-doc] docs/runtimes/codex.md — This page discusses CODEX_HOME, .env filtering and the hook environment variables, but has no cross-reference to the new sandbox environment contract (docs/normative/sandbox-env/v1/README.md). docs/runtimes/claude.md and docs/runtimes/pi.md both received one in this PR.
    Remediation: Add a note linking to docs/normative/sandbox-env/v1/README.md, stating that CODEX_HOME and the keys reserved from env.sandbox are published in the sandbox environment contract.

  • [incomplete-doc] docs/runtimes/opencode.md — This page discusses OPENCODE_CONFIG_CONTENT and env.sandbox configuration, but has no cross-reference to the sandbox environment contract, which publishes OpenCode's runtime-specific exports. claude.md and pi.md both link to it.
    Remediation: Add a link to docs/normative/sandbox-env/v1/README.md explaining that OpenCode's runtime-specific exports and reserved sandbox keys are published in the sandbox environment contract manifest.

  • [backward-incompatible] docs/normative/sandbox-env/v1/README.md:208 — The versioning table classifies only additions to variable sets as non-breaking. It does not say whether removing a variable from exports, reserved or another category is breaking, and it lists 'add or remove deprecated entries' as non-breaking. A consumer pinned to v1 is not told explicitly whether a variable can disappear, or a reserved key be added, without a v2 bump.
    Remediation: State explicitly whether removing a variable from any category, or adding a reserved key, requires a v2 bump, or document the policy.

Earlier findings

  • docs/normative/sandbox-env/v1/README.md:126, missing-deprecation — reclassified from low to info: the deprecated array is empty and nothing is deprecated in this PR, so the missing lifecycle policy is a forward-looking suggestion.
  • docs/problems/agent-infrastructure.md:124, doc-style — remains info (previously reclassified); below the posting threshold.
  • internal/envmanifest/builder.go:76, api-shape — remains info (previously reclassified); below the posting threshold.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added component/sandbox OpenShell sandbox environment component/docs User-facing documentation type/adr labels Oct 21, 2026
@jflowers
jflowers force-pushed the opsx/sandbox-env-manifest branch from e38def5 to 8e22ebc Compare October 21, 2026 20:47
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 8:49 PM UTC · Ended 9:03 PM UTC

Commit: 8e22ebc · View workflow run →

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Site preview

Preview: https://fb7c8f82-site.fullsend-ai.workers.dev

Commit: d02ad0f9d5cf9d224921b9c015a56b8f00e31d1a

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:49 PM UTC · Completed 9:03 PM UTC

Commit: 8e22ebc · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.40

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:39 PM UTC · Completed 10:54 PM UTC

Commit: 405ae6c · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.27

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:00 AM UTC · Completed 6:14 AM UTC

Commit: bc697d2 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.46

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:26 AM UTC · Completed 4:48 AM UTC

Commit: a85603c · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.25

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:23 PM UTC · Completed 1:38 PM UTC

Commit: caefb51 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $2.93

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:54 PM UTC · Completed 5:16 PM UTC

Commit: cb829d0 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $2.61

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:50 PM UTC · Completed 10:07 PM UTC

Commit: c1df2b7 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $4.28

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:11 AM UTC · Completed 12:25 AM UTC

Commit: a7e0504 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.32

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:48 AM UTC · Completed 5:02 AM UTC

Commit: 411f083 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.06

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:36 PM UTC · Completed 5:49 PM UTC

Commit: 747cd43 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.11

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:31 PM UTC · Completed 10:43 PM UTC

Commit: 6e98761 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.37

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:28 AM UTC · Completed 11:40 AM UTC

Commit: d28e7ef · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $2.95

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:37 PM UTC · Completed 5:49 PM UTC

Commit: a3bf259 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $2.81

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:57 PM UTC · Completed 10:08 PM UTC

Commit: 96905b0 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $2.89

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:22 AM UTC · Completed 1:39 AM UTC

Commit: 9a5adaa · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.64

fullsend-ai-review[bot]

This comment was marked as outdated.

The environment variables the runner injects into sandboxes and the
names reserved from env.sandbox overrides were documented as scattered
prose. Codify them as a versioned, generated contract so runtimes and
harness authors can rely on a single normative artifact.

- Add internal/envmanifest package: v1 manifest types and builder
- Publish docs/normative/sandbox-env/v1/ (README, generated manifest,
  JSON schema), pinned to actual runner behavior by collector and
  golden tests in internal/cli
- Record the decision in ADR 0146
- Cross-reference the contract from CLI, harness, runtime, and agent
  guides

Assisted-by: glm-5
Generated with AI assistance (glm-5)
AI-Assisted: true

Signed-off-by: Jay Flowers <jay.flowers@gmail.com>
- Reformat Options section into bullet points
- Rephrase "collector" and "production emitters" for accessibility
- Clarify fullsend_version line (tag is the version, no field removed)
- Add required ## Forge scope section
- Wrap long lines in security-threat-model.md and architecture.md

Addresses PR fullsend-ai#8679 review feedback from @rh-hemartin and
fullsend-ai-review[bot].

AI-Assisted: true
Assisted-by: claude-opus-4
Signed-off-by: Jay Flowers <jay.flowers@gmail.com>
Fix test stub argument position ($4→$5) to match production openshell
upload arg order, add exec arms for ClaudeRuntime.Bootstrap probes, and
filter hookEnvExportKeys to .env-targeting lines only.

Reclassify iteration-env trio (FULLSEND_TIMEOUT_MINUTES,
FULLSEND_ITERATION_DEADLINE, TRACEPARENT) from always to conditional
since writeIterationEnv failure is tolerated. Exclude readonly
non-exported env-data vars (FULLSEND_RUNNER_PATH, FULLSEND_ENV_PYTHON,
FULLSEND_TRUSTED_PATH, FULLSEND_READLINK, FULLSEND_ENV_EXPORTS) from
the manifest exports.

Union sandbox.ReservedCredentialKeys() into the reserved set so
runtime-blocked keys (HTTPS_PROXY, NODE_OPTIONS, GIT_SSH_COMMAND, etc.)
appear in the published contract alongside reservedSandboxKeys.

Add deprecated category to the manifest schema, builder, normative
spec, and golden test expectations.

Fix normalized() Conditional map aliasing (clone instead of reference).

Update ADR 0146 with commit-SHA permalinks for code citations and
correct ADR 0038 attribution (tag pinning complements, not replaces,
the commit-SHA + integrity-hash mechanism).

Regenerate golden manifest.

Addresses PR fullsend-ai#8679 review findings.

Signed-off-by: Jay Flowers <Jay.Flowers@gmail.com>
AI-Assisted: true
Assisted-by: claude-opus-4
Signed-off-by: Jay Flowers <jay.flowers@gmail.com>
…en test claim

Addresses qodo review findings:
- Remove standalone References section (not in ADR template); all links
  already appear inline in Context, Decision, and Consequences.
- Add ADR 0038 cross-reference in Context where the tag-pinned topology
  is introduced.
- Add agents#1672 follow-up reference in Context.
- Fix misleading claim that golden test catches new runtimes — it is
  TestEnvManifestRuntimeEnumeration that pins the runtime list; the
  golden test covers the committed manifest content.

Signed-off-by: Jay Flowers <jay@unbound.force>
AI-Assisted: true
Signed-off-by: Jay Flowers <jay.flowers@gmail.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:45 AM UTC · Completed 7:00 AM UTC

Commit: d02ad0f · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5-5 · Effort: high · Cost: $3.07

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

for _, name := range productionSandboxRuntimes {
backend, err := agentruntime.Resolve(name)
require.NoError(t, err, "resolving runtime %q", name)
b.AddRuntimeSpecific(name, parseExportedEnvKeys(backend.EnvExports()...)...)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] api-contract

The collector omits variables exported by runtime launch commands outside EnvExports(). For example, buildPiRunCommand exports FULLSEND_RUNTIME, FULLSEND_PI_MANIFEST and GOOGLE_CLOUD_LOCATION (pi_run.go), and the Codex and OpenCode launch commands export FULLSEND_RUNTIME. None appears in the committed manifest, and changes to these emitters cannot affect the golden result. This makes the advertised inventory of runner-injected sandbox variables (the README claims it covers every code path that injects environment variables) incomplete, and downstream reachability checks would reject valid variable references.

Suggested fix: Collect the launch-command exports with their runtime and feature conditions, or explicitly narrow the contract and provide a separate inventory for those runner-injected variables.

// envManifestKeyRe matches a KEY= assignment token while excluding $KEY
// references: an emitter's output assigns the names it exports and reads the
// ones it prefixes with $.
var envManifestKeyRe = regexp.MustCompile(`(?:^|[^A-Za-z0-9_$])([A-Za-z_][A-Za-z0-9_]*)=`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] test-inadequate

The export collector (envManifestKeyRe) recognizes KEY= assi... rather than exported variables. Both 'export CODEX_HOME=...' and 'CODEX_HOME=...' produce the same key, so dropping 'export' can break the agent environment without changing the manifest or failing the golden test. The regex also matches assignment-like text inside quoted values (e.g. a URL containing '?PHANTOM=value' yields a nonexistent PHANTOM variable). The hand-maintained readonly exclusion list does not address either behavior.

Suggested fix: Use export-aware parsing or inspect the resulting exported environment, and add regression tests for unexported assignments and assignment-like text inside values.

"FULLSEND_FETCH_TOKEN": "the runtime fetch service is enabled (ADR 0038)",
"FULLSEND_TRACE_ID": "trace-ID injection succeeds; the run warns and continues when it fails",
"TIRITH_FAIL_ON": "security.sandbox_hooks.tirith.fail_on is set",
"TIRITH_REQUIRED": "Tirith scanning is required (security.sandbox_hooks.tirith.enabled, default true)",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] api-contract

The gates for the hook variables (TIRITH_REQUIRED, TIRITH_FAIL_ON, FULLSEND_EGRESS_ALLOWLIST) do not state which runtimes support them. The collector only runs ClaudeRuntime.Bootstrap; OpenCodeRuntime.Bootstrap never calls appendHookEnv, yet opencode is a production runtime in the manifest. Practical impact is small because opencode fails closed when hooks are enabled (opencode_run.go), but the gate descriptions are incomplete as a contract statement.

Suggested fix: Add the supported-runtime restriction (claude, codex, pi) to these gate descriptions, or document that opencode currently fails closed when hooks are enabled.

configuration changes the runtime.

The enumerated runtimes are the runner's resolution registry minus test
stubs. A runtime that exports nothing appears with an empty set — `opencode`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] incorrect-doc

README says 'A runtime that exports nothing appears with an empty set — opencode — rather than being omitted.' but sandbox-env.json lists three opencode exports (OPENCODE_CONFIG_DIR, OPENCODE_DISABLE_EXTERNAL_SKILLS, OPENCODE_DISABLE_PROJECT_CONFIG), as does the abridged example near line 59. The collector comment near line 41 repeats the stale claim.

Suggested fix: Remove the opencode example or state that opencode currently exports those three variables; fix the collector comment too.

- [ ] `internal/cli/env_manifest_collector_test.go` and
`internal/cli/env_manifest_golden_test.go` — the runtime's `EnvExports()`
names must land in the generated
[sandbox environment contract](../normative/sandbox-env/v1/README.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.

[low] incorrect-doc

Line 67 says registering a runtime without a manifest decision fails the golden test; at HEAD this fails TestEnvManifestRuntimeEnumeration, while TestEnvManifestGolden only byte-compares the committed manifest against the collector output. ADR 0146 and the normative README distinguish the two but this checklist does not.

Suggested fix: Name TestEnvManifestRuntimeEnumeration for registration drift and 'TestEnvManifestGolden -update' for regenerating the manifest.

if the runtime is user-selectable).
- [x] `internal/config/config_test.go` — update any assertion on
`ValidRuntimes()` to include the new name.
- [ ] `internal/cli/env_manifest_collector_test.go` and

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] pattern-inconsistency

Every other entry under 'Consumer-completeness touchpoints' uses '- [x]', but the newly added test checklist item at line 64 uses '- [ ]'.

Suggested fix: Change '- [ ]' to '- [x]' on line 64.

entries naming these keys are skipped with a warning: the runner removes
them so harness configuration cannot shadow runner infrastructure, inject
OIDC and provider credentials, or override security-critical process
variables (e.g. `HTTPS_PROXY`, `LD_PRELOAD`, `NODE_OPTIONS`). A key may appear both here and in an export

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] doc-style

Line 121 is 105 characters long, violating the Markdown prose wrapping rule (~80 columns) while surrounding lines are wrapped.

Suggested fix: Wrap the sentence at ~80 columns.

This branch was successfully deployed

2 active deployments
site-preview — d02ad0f9 Deployed Oct 27, 2026 by github-actions[bot]
dev — d02ad0f9 Deployed Oct 27, 2026 by jflowers via playback #18039
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/docs User-facing documentation component/sandbox OpenShell sandbox environment risk/moderate PR risk: moderate type/adr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Machine-checkable sandbox env-var contract (manifest of exported/reserved sandbox variables)

2 participants