fix(config): keep skill block from writing a second skillForge key - #500
Conversation
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: replace the malformed Claude model ID in the commit trailer
The code change itself looks correct, but the commit records Co-authored-by: Claude (claude-opus-5[1m]) .... The [1m] suffix is terminal-formatting residue, not the actual model ID required by AGENTS.md section 3.3, and squash merging would preserve incorrect contribution provenance. Please rewrite that trailer with the actual current-session model ID.
I reviewed the full diff; the set_skill_blocked callers; raw-config loading, migration, and RavenConfig alias behavior; relevant history; backward compatibility for camelCase and snake_case files; the added tests for weakening; and the applicable AGENTS.md, CLAUDE.md, CONTEXT-MAP.md, and architecture constraints. I found no code-level issue. uv run pytest tests/test_config_update.py tests/test_cli_skill_commands.py -q passed: 55 tests in 9.18s.
|
Thanks for the depth here -- the callers, the migration paths and the alias behaviour were On the trailer I disagree, and I would rather show the checks than assert it. 1. It cannot be ANSI. ANSI SGR bold is The trailer carries a literal 2. It is already an established spelling on 3. Removing the suffix would introduce the misattribution the rule exists to prevent. The repo's own gates accept the string as written: So I have left the trailer as it stands rather than rewriting history for it, which also If you still read the bracketed form as wrong, I would rather we change the convention than |
|
No blockers; this can merge as far as I am concerned. You are right, and I withdraw my trailer objection. I incorrectly treated the visible The code review had no other findings, so my blocker is fully lifted. I reran |
set_skill_blocked wrote data.setdefault("skillForge", {}) unconditionally.
Dict keys match byte for byte, but the schema treats skillForge and
skill_forge as one field (alias_generator=to_camel with populate_by_name),
so a config file spelling the block skill_forge gained a second block
instead of having its own one patched.
RavenConfig forbids extra inputs, so load_raven_config then binds the alias
and rejects the leftover field name. raven agent, raven gateway and the TUI
RPC server all refuse to start. raven status keeps working, because the base
loader pops extension keys before validating, which hides the breakage.
Two quieter faces of the same read: skill unblock became a silent no-op on
such a config, and blocking an already-listed skill reported it as newly
blocked, both because the blocklist was read from the empty block the call
had just created.
Probe the spelling the file already carries and reuse it, the same shape
loader.py already uses for the skillRouter and everosSkillLight migrations.
Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
The same branch made set_skill_blocked reuse whichever spelling the config file already carries, so the key it writes is now conditional. Two lines that report what the write just did were not: the log line, and the result line of raven skill block and raven skill unblock, both named skillForge.blocklist unconditionally. On a file spelling the block skill_forge the write lands on skill_forge.blocklist while both messages say skillForge.blocklist, so an operator sent to their own config greps for a key it does not carry. Report the setting rather than the file key. That is what the sibling set_sentinel_nudge_quota already does: it respects the same two spellings and logs "sentinel nudge quota patched" with no key path at all. The --help text and the module docstring keep naming skillForge.blocklist, because they describe the command rather than the write, and that sibling names one canonical spelling in its own summary line the same way. Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
e49d6d2 to
4af8cb0
Compare
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
I reviewed the rebased full diff and used range-diff to separate the new main history from PR-owned changes. The original config fix is preserved; the new commit correctly makes the CLI and log wording casing-neutral without changing values or exit behavior, and its snake_case regression test exercises the operator-visible failure it prevents.
Coverage included the diff, the skill block/unblock callers, config loader and alias behavior, commit history and rebase resolution, camelCase backward compatibility, test-strength changes, and the applicable AGENTS.md, CLAUDE.md, CONTEXT-MAP.md, and architecture constraints. There are no review threads to resolve. uv run pytest tests/test_config_update.py tests/test_cli_skill_commands.py -q passed: 68 tests in 12.55s.
|
Not a blocker. The fix is right and its own tests pin it: reverting the 1. Two of the three reworded strings are pinned by nothingThe PR changes three user-facing strings away from Two thirds of this change's own point can be undone later without a red test, The cheap close: 2. The block-selection rule now has three copies and still no ownerThe new block at and I do not think a bug fix has to carry the extraction, which is why this is an 3. The new guard reads as if it handles a non-dict value, and does not
This predates the PR: base's |
Summary
raven skill blockcould leave a config file that Raven then refuses to load.set_skill_blockedwrotedata.setdefault("skillForge", {})unconditionally. Dict keysmatch byte for byte, but the schema treats
skillForgeandskill_forgeas one field(
alias_generator=to_camelwithpopulate_by_name), so a config file spelling the blockskill_forgegained a second block instead of having its own one patched.RavenConfigforbids extra inputs, so
load_raven_configbinds the alias and rejects the leftover fieldname:
raven agent,raven gatewayand the TUI RPC server all then refuse to start.raven statuskeeps working throughout, because the base loader pops extension keys beforevalidating. That asymmetry is what makes the defect quiet: the command most people reach for
to check a config is the one that cannot see it.
The same misread has two softer faces, both closed by the same change.
raven skill unblockbecame a silent no-op on such a config, reporting success while the skill stayed blocked;
and blocking an already-listed skill reported it as newly blocked. Both read the blocklist
out of the empty block the call had just created.
The fix probes the spelling the file already carries and reuses it. That is not a new shape:
loader.pyalready does exactly this for theskillRouterandeverosSkillLightmigrations, and
set_sentinel_nudge_quotadoes it forsentinel.nudge_policy. Among thetop-level blocks a config writer creates,
skillForgeis the only one whose camelCase andsnake_case spellings differ at all.
The second commit closes what the first one opened. Making the written key conditional made
two lines that report the write false on a snake_case file: the log line, and the result
line of
raven skill blockandraven skill unblock, both of which namedskillForge.blocklistunconditionally. Measured on askill_forgeconfig, the write landedon
skill_forge.blocklistwhile both messages still saidskillForge.blocklist, sending anoperator to grep their own config for a key it does not carry. They now report the setting
rather than the file key, which is what
set_sentinel_nudge_quotaalready does: it respectsthe same two spellings and logs
sentinel nudge quota patchedwith no key path at all. The--helptext and the module docstring keep namingskillForge.blocklist, because theydescribe the command rather than the write.
Checked and deliberately not fixed, with reasons:
raven skill blockagain now selects the camelCase block and leaves the snake_case one in place, so such a
file still needs a hand edit. Merging the two blocks automatically is lossy and ambiguous,
and belongs in a migration argued on its own terms rather than inside a fix whose job is
to stop producing the state.
update_cron_configwritesto_camel(key)unconditionally, which is the same shape onelevel down, on field keys rather than block keys. Its failure is milder and different:
CronConfigdoes not forbid extras, so a file carrying bothdefault_timezoneanddefaultTimezoneloads with the camelCase one winning. Measured directly. Nothing breaks,but a later hand edit of the snake_case key is silently dead. Pre-existing, a different
failure mode, and in another config surface.
raven/agent/tools/skill_hub.pynameskillForge.blocklistin text the model reads. These were already inaccurate for ahand-written
skill_forgeconfig before this branch, since the loader has always acceptedboth spellings on the read side. Not introduced here, and fixing them widens the diff into
the agent tool surface.
Type
Verification
Run on Linux with Python 3.12.13, on a branch rebased onto the current
main(which nowcarries the A2A work). The rebase conflicted only in
tests/test_config_update.py, whereboth sides appended a section at the end of the file; both sections were kept. The rebased
commit's diffstat is byte for byte the pre-rebase one, +70/-1 over two files, so the
resolution neither absorbed nor dropped anything.
uv run --frozen --all-extras pytest tests/test_config_update.py-> 47 passeduv run --frozen --all-extras pytest tests/ -k "config or skill_forge or skill_block"->1709 passed, 33 skipped, 0 failed
make lint-python-> ruff check clean, 2008 files already formattedmake lint-imports-> 10 contracts kept, 0 brokenmake lint-deps-> no dependency issuesmake lint-types-> all checks passedmake check-commits,make check-large-files,make check-source-language-> exit 0Neither commit's tests are theatre, and each was checked on its own. Reverting only the
first commit's production hunk turns 4 of its 5 tests red: duplicate key,
load_raven_configValidationError, unblock no-op, and blocklist read from the wrong block.The fifth covers camelCase behaviour that predates the fix and correctly stays green.
Restoring the message wording the second commit replaced turns its new CLI regression red on
the
skillForgeassertion, and restoring the fix turns it green.End to end with an isolated
RAVEN_HOME: a config carrying the duplicate block makesraven agentexit withValidationError: skill_forge Extra inputs are not permitted. Thesame starting config driven through the fixed
raven skill blockgets past configvalidation and reaches the expected missing-API-key error, with the user's existing
auto_installsetting preserved beside the new blocklist.Malformed blocks were checked too.
skillForge: nullbeside askill_forgetable used toraise
AttributeErrorand now works.skillForge: nullalone, andskillForgeset to astring, still raise
AttributeError, unchanged from before.File mode was measured rather than assumed, because the new A2A onboarding narrows
config.jsonto 0600 and this branch writes to the same file: afterinitialize_a2a_server, araven skill blockleaves the file at 0600, sinceatomic_updatepreserves the mode it finds.make lintalso runslint-ui, which fails in a fresh worktree withERR_MODULE_NOT_FOUNDbecause
ui-web/node_modulesis not installed there. This change touches no JS or TS file,so the Python lint targets above are the gates that cover it. No user-facing docs were
needed; the helper's docstring states the casing contract.
Risk
Behaviour changes only for a config file that spells the block
skill_forge. For a fileusing the camelCase spelling, which is what onboarding writes, the bytes written are
identical to before.
Two changes are visible to a snake_case user, and both are the point of the fix: the
blocklist is written into their existing section rather than into a new one, and
raven skill unblocknow actually removes an entry instead of reporting success and doingnothing.
One change is visible to every user regardless of spelling: the result line of
raven skill blockandraven skill unblocknow readsSkill blocklist = [...]where itread
skillForge.blocklist = [...], and the corresponding log line drops the key path thesame way. This is wording only; no value or exit code changes, and the
--helptext stillnames
skillForge.blocklist.Rollback is a revert of the two commits. No migration runs, so nothing has to be undone on
disk. Reverting only the second leaves the first in place with the messages inaccurate on a
snake_case config, so revert both or neither.
No permissions, credentials or file modes are changed; the 0600 measurement above is the
check behind that sentence.
Related Issues
N/A