Repository navigation
fix: one output_dir boundary check for run_skill admission and recipe rules - #5257
Merged
Trecek merged 12 commits intoOct 10, 2026
Conversation
…hority output_dir_widens_scope decides whether a dispatch output_dir widens a skill's declared write scope, matching the scope kind before calling bounded_scope_contains. widened_output_dir_message is the diagnostic that names the skill, the requested value, and the declared directories. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_extend_closure_write_scope now decides through output_dir_widens_scope and reports widened_output_dir_message, so a rejected dispatch says which skill, which output_dir, and which declared directories were involved. Admission semantics and the closure union extension are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elper skill_write_scope resolves a SKILL.md, reads it once, and decodes its write_paths declaration. The alignment rule now reads SKILL.md through it; its findings are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
write-skill-requires-source-output-dir keyed on write_behavior and demanded an output_dir from BOUNDED skills too, recommending a worktree-root value that run_skill rejects for them. It now fires only when the skill declares write_paths: unrestricted and the step omits output_dir; a BOUNDED skill's temp floor is already its declared scope. The duplicate test_write_skill_steps_have_output_dir is removed; the real rule's bundled sweep in test_rules_contracts covers it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…outputs
vis_apply passed output_dir run-vis-lenses to vis-lens-{slug} skills, each of
which may write only to its own vis-lens-<slug>/ directory, so run_skill
rejected every lens dispatch; vis_synthesize then read run-vis-lenses, a
directory no skill writes.
vis_apply now passes no output_dir, and synthesize-vis-plan takes the
captured vis_lens_output_paths list (lens_output_paths, optional) instead of
a capture directory. research-design declares the capture optional because
vis_apply routes failures to vis_synthesize.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ktree Seventeen bundled steps passed output_dir: . to make-plan, prepare-pr, prepare-research-pr and compose-research-pr. Those skills declare BOUNDED write scopes, so run_skill rejects a worktree-root output_dir for them. With output_dir omitted, the default floor <cwd>/.autoskillit/temp/<skill> equals each skill's declared scope; captures, plan placement and attestation are unchanged. Regenerates the compiled recipes, the diagram recipe hashes and the pinned pre-delivery structure digests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t rule
skill-write-path-recipe-alignment pre-filtered '.', '${{ context.work_dir }}'
and slash-only values before its boundary check, so it stayed silent on the
exact dispatches run_skill rejects. The rule now resolves each value to the
path run_skill will judge -- '.', slash-only, or a value rooted at the step's
cwd -- and decides through output_dir_widens_scope, reporting the runtime's
own diagnostic. Only statically undecidable values (another template root,
an absolute path) stay silent.
bounded_scope_contains is registered as a single-enforcement-point policy
function: output_dir_widens_scope is now its only caller.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_bundled_dispatch_write_scope_admission dry-runs every tracked bundled
run_skill step through the real _resolve_dispatch_paths and
_extend_closure_write_scope, asserting each is admitted and that the
alignment rule flags exactly what the runtime would reject. Planner steps
run through a reviewed probe for ${{ context.planner_dir }}, templated-skill
dispatches must pass no output_dir, and positive controls keep the sweep
non-vacuous.
test_attested_dispatch_write_scope_admission drives the real attested
run_skill for implementation:plan and implementation:prepare_pr and pins the
narrowed write-watch directory. ADR 0015 names the shared predicate and the
drift guard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dropping output_dir: . from remediation's make_plan and prepare_pr steps shrinks the rendered remediation recipe; the canonical baseline maxima are re-measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Trecek
deleted the
impl-rectify_bounded_output_dir_write_target_authority-20261009-205759
branch
October 10, 2026 17:28
This was referenced Oct 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #5256
Summary
Bundled recipes passed
output_dir: .to skills whosewrite_pathsis BOUNDED.run_skillcorrectly rejects those dispatches, because.would widen the grant to the whole worktree. The static rules never caught it: four places each answered "is thisoutput_diradmissible for this skill?" on their own, and they had drifted apart. Since #5211 moved 12 bundled skills to BOUNDED scopes, 17 bundled steps have been rejected at runtime, while every static check stayed silent or even required the rejected value.This PR gives that question one answer and fixes the dispatches.
Changes
output_dir_widens_scope(scope, output_dir, project_dir)and the shared diagnosticwidened_output_dir_message(...)now live in the stdlib-only write-scope authority,hooks/_write_scope.py.run_skilladmission (_extend_closure_write_scope) and theskill-write-path-recipe-alignmentrule both decide through it.bounded_scope_containsis registered inPOLICY_FUNCTIONS, so this check stays its only caller.output_dir, and the declared directories. Admission behavior and the closure union extension (Cook sessions cannot compose skills: write boundary intersects across loaded skills, and composable sub-skills like prepare-issue are unusable #5154 / feat: compose skill write scopes by union with typed write_paths declarations #5211) are unchanged..,${{ context.work_dir }}and slash-only values is gone.run_skillwill judge:., slash-only, or rooted at the step'scwd.write-skill-requires-source-output-dirnow fires only for skills declaredwrite_paths: unrestrictedthat omitoutput_dir. The duplicatetest_write_skill_steps_have_output_diris removed, since the real rule's bundled sweep covers it.output_dir: .is dropped from the 17 BOUNDED dispatches inimplementation,implementation-groups,remediation,merge-prs,research,research-implementandresearch-review. Those skills aremake-plan,prepare-pr,prepare-research-prandcompose-research-pr. The default floor<cwd>/.autoskillit/temp/<skill>equals each one's declared scope.vis_applypassedoutput_dir: run-vis-lensestovis-lens-{slug}skills, so the runtime rejected every lens dispatch. It now passes nooutput_dir.synthesize-vis-planpreviously read a directory no skill writes. It now consumes the capturedvis_lens_output_pathsthrough a new optionallens_output_pathsinput.Drift guard
tests/server/test_bundled_dispatch_write_scope_admission.pydry-runs every tracked bundledrun_skillstep through the real_resolve_dispatch_pathsand_extend_closure_write_scope. It asserts that each step is admitted and that the alignment rule flags exactly the steps the runtime would reject.${{ context.planner_dir }}.output_dir.tests/server/test_attested_dispatch_write_scope_admission.pydrives the real attestedrun_skillforimplementation:planandimplementation:prepare_pr. It pins the narrowed write-watch directory.test_rule_is_exercised_not_vacuously_silentproves that the bundled zero-findings test is earned.Verification
vis_applytemplated-skill violations.output_dironvis_apply, and disabling the rule's step selection.implementation.yamlwithoutput_dir: .restored onplanreports oneskill-write-path-recipe-alignmentERROR that carries the runtime's own diagnostic.a9a8ed994exits 0:configured_output_dir: ""givesconfigured_output_dir_widens: false;.givesdot_output_dir_widens: true.tests/recipe,tests/arch,tests/hooks,tests/workspace,tests/infra,tests/contracts,tests/skills_extendedand the recipe-loadingtests/serverfiles pass.pre-commit run --all-filesand the complexity ratchet againstdeveloppass.task test-local-gatehas not been run.Commissioning still pending (post-merge)
Per #5256, run the PR #5255 implementation E2E scenario on MiniMax twice against sandbox seed
TalonT-Org/autoskillit-e2e-sandbox#1, and confirm that both passplanandprepare_pradmission. Do that before claiming commissioning. This PR does not close the issue for that reason.Notes for reviewers
.,/, or acwd-equal template to a BOUNDED skill now get a static ERROR carrying the runtime's diagnostic. The git-excluded local.autoskillit/recipes/remediation-fable.yamlwill report it on its make-plan, prepare-pr and compose-pr steps. Its owner should dropoutput_dirthere.allowed_write_prefixesand the write-watch snapshot narrow from the worktree to the skill's own directory. Each skill's NEVER block already confines it there, and none of these skills is inWORKTREE_SKILLS.🤖 Generated with Claude Code