Repository navigation
Conversation
…ending the block
The generated OpenCode adapter is an auto-loaded module, so it is installed as a
managed block rather than a whole-file write. When the file on disk carried
content but none of our markers, the upsert appended the block anyway, even
though the block's own header tells readers to "remove the markers to take
ownership".
The appended prelude redeclares module-level names the user's own module also
declares -- `import { spawn }`, `const BIN`, `function augment` -- and both
sides declare a default export once the block itself carries one, so the result
cannot be parsed at all:
SyntaxError: Identifier 'spawn' has already been declared
The client then drops the whole extension with only a load warning to show for
it. A hand-port around DeusData#2204 is therefore not a durable end state.
Probe block ownership before writing. A marker-less file that carries content is
left alone and reported in both the real run and the dry run, instead of being
turned into an unparseable hybrid. An absent, empty, whitespace-only, or
already-ours file still installs or refreshes exactly as before.
Refs DeusData#2204
Signed-off-by: Cristian Castillo <c.a.c.m997@gmail.com>
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
DeusData
left a comment
There was a problem hiding this comment.
Thank you, @crisacm, for this fix and for the unusually thorough write-up. The before/after table and the pre-existing-failure comparison made it easy to follow. The behavior is right: the generated header says "remove the markers to take ownership", so a marker-less file at that path is the user's, and leaving it alone and saying so is what the installer should do. Several people hit this on #2204, so this will help.
CI stopped at lint, so the tests never ran. Two changes, then CI can run the suites:
-
Memory-core ratchet.
lint-memory-corefails becausesrc/cli/config_text_edit.cgains two rawfree()calls (72 -> 74). We route allocations throughsrc/foundation/mem_core.h, and the linter only lets each file's count of raw allocator calls go down.text_read_file()still hands back memory from plainmalloc, so swapping incbm_freeis not a drop-in fix here. The simplest way through is to let the probe reuse the read-validate-scan step thattext_upsert_managed_block()already performs (for example a small shared static helper, or a probe-only mode), so no newfree()site is added. You can check it locally withmake -f Makefile.cbm lint-memory-core. (Thecypher.cline in the same log asks us to tighten our own baseline; that one is not yours.) -
Install-plan receipt. When
g_install_planis set,install_generated_client_extension()still callsplan_record(label, "extension", path)before the ownership check, so the plan receipt lists a write that the real run will not make. The receipt is built from the same code path precisely so it cannot drift from the run. Could you skipplan_record()when the file is foreign? The message you print there already explains the skip. A check for that in your new cli test would be welcome if it is easy.
Your branch still merges cleanly with current main, so no rebase is needed. Thanks again for the careful work!
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…robe cbm_text_managed_block_status() repeated the read, validate and scan step of text_upsert_managed_block() with two raw free() calls of its own, which put src/cli/config_text_edit.c over its memory-core baseline (72 -> 74). Both now go through text_read_managed_document(), so the probe and the writer judge a file by the same rules, and the file is back at 72. An absent file still reads as an empty document, so the probe returns the same results as before. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
With --plan, install_generated_client_extension() recorded the extension before the ownership check and printed the skip notice on stdout. The receipt therefore listed a write the run would not make, and `install --plan` no longer printed valid JSON when a user-owned module was present. For a user-owned module, plan mode now records and prints nothing. The dry run prints the "would be skipped" notice instead, alongside the hook-script notice from DeusData#1387, and the real run keeps its "left untouched" line. The cli test now also checks the receipt and plan-mode stdout, with a control that the receipt does list the module once it is no longer user-owned. Refs DeusData#2204 Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Thank you, @crisacm, for this fix and for the care that went into it. The write-up, the before/after table and the side-by-side run showing that the one red cli test fails on main too made this easy to review, and the behavior is exactly right: a module at that path without our markers is the user's, and the installer should leave it alone and say so. Several people ran into this on #2204, so this will help them directly. To save you the round trip, we made the changes from our review ourselves and pushed them on top of your branch. Your commit is unchanged; here is what we added after it:
With these, the full cli suite (363 tests) passes locally. Once CI is green we will merge. Thanks again for a really careful contribution! |
text_upsert_managed_block read doc.bom_len into a function-scope local, but only the "no managed region yet" branch uses it. cppcheck 2.20 (the CI lint image) flags that as variableScope. Declare it inside the else branch instead; doc stays in scope and unmodified, so behaviour is unchanged. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Hi @crisacm, thank you again for this fix and for your patience with us here! This is a small follow-up to our own earlier fix-up on your branch, not to your change. Our refactor commit ("share the managed-block read step with the ownership probe") left a variable in a wider scope than it needs, and the cppcheck version our CI lint job uses flagged it.
No action is needed from you. Once CI is green we will take it from here. |
Summary
The generated OpenCode adapter (
~/.config/opencode/plugins/cbm-augment.ts) is an auto-loaded module, so it is installed as a managed block rather than a whole-file write. That is deliberate: the directory is auto-loaded, so a user may legitimately keep their own module there.But when the file on disk carried content and none of our markers, the install appended our block to it anyway — even though the block's own header tells readers to "remove the markers to take ownership". That contradicts the promise and produces a file the client cannot parse.
Reproduction
install -yagainst 0.11.0, in an isolatedHOME, starting from the hand-port shape described in #2204:After the run the managed block sits after that content, so module-level names are declared twice:
OpenCode's loader then drops the whole extension and the only trace is a load warning. Once the block itself carries
export default(#2089), the same append turns into a duplicate default export instead. So a hand-port is not a durable end state — which is why #2385 matters — but the installer should not manufacture the broken file in the meantime.Change
cbm_text_managed_block_status()— a read-only ownership probe:0writable (absent, whitespace-only, or already ours),1the file carries content but none of our markers,-1unsafe state or I/O failure.install_generated_client_extension()probes before writing:--dry-run. The probe runs before generation so the preview names the refusal instead of promising a write the run will not make (the same honesty rule install on 0.9.1-rc.1 removes existing Claude Code hooks instead of installing them #1387 established for hook scripts).cbm_text_upsert_managed_block()and its other callers (instruction documents, where appending to a user's file is the intent) are untouched — the new semantics are scoped to the auto-loaded module case.If you would rather the run fail loudly than skip, that is a small change to the same call site; I picked "leave alone and say so" because it matches the ownership wording in the generated header.
Verification
End-to-end in isolated
HOMEs on the built binary:SyntaxErrorTests:
scripts/test.sh --suites config_text_edit— 23 passed, including the newconfig_text_managed_block_status_reports_foreign_module(absent / empty / whitespace / ours / foreign / unbalanced markers / malformed markers).scripts/test.sh --suites cli— 361 passed, 1 failed, and the newcli_extension_leaves_user_owned_module_untouched_issue2204is the+1over the baseline: it seeds a user-owned module, runs the dry run, and asserts both the warning and that the file is byte-identical afterwards (modelled on the existingcli_dry_run_predicts_refused_hook_script_issue1387).The single failure is pre-existing and unrelated. Same machine, same suite, same test:
mainat0ca6e7d(no changes)In both trees the failing test is
cli_registry_installs_gitlab_and_devin_lifecycle_contextwith the identical diagnosticplan=1 clean_plan=1 installed=0 cleaned=1 gitlab=1 devin=1 uninstall=0— it touches neither the OpenCode adapter nor the managed-block writer. Flagging it here so a redtest-unixjob is not read as fallout from this change.clang-format 20.1.8(CI's pinned version) reports no changes to any touched file.Refs #2204. The template side — registering the 2.x hooks in
setup— is #2385 and is deliberately not touched here. Happy to fold this into #2385 instead if you would rather keep the OpenCode changes on one branch.