Race-detector CI runs (-race) slow goroutines enough that a
prior sweeper goroutine (e.g. TestStartSweeper_TransientErrorDoesNotCrashLoop)
can still be running and incrementing pendingUploadsSweepErrors after
metricDelta() captures its baseline, but before the success-path sweeper
records its success metrics. The test then reads deltaError=1 instead of 0.
Fix: add waitForMetricDelta(t, deltaError, 0, 2*time.Second) before the
assertion, matching the polling pattern already used in the error-path
test (TestStartSweeper_RecordsMetricsOnError). This ensures the error
counter has settled before we assert on it.
Fixes molecule-core#22.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cmd/Ctrl+Arrow Up/Down resizes node height (±10px, ±2px with Shift).
Cmd/Ctrl+Arrow Left/Right resizes node width (±10px, ±2px with Shift).
Uses the same onNodesChange('dimensions') path that NodeResizer uses
— no new store action needed. Respects min-width/min-height matching
the NodeResizer constraints (360×200 with children, 210×110 without).
The Arrow-key move shortcut now skips when a modifier key is held,
so Cmd/Ctrl+Arrow unambiguously means resize (not move).
Updates canvas audit doc: Node Rendering section updated and
the LOW node-resize item marked done. All Remaining Gaps items
are now complete.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Issue #75 PR-D: two remaining `gh` CLI calls in .github/workflows/.
1. ci.yml canvas-deploy-reminder:
- Replaced `gh api POST repos/.../commits/.../comments` with writing
to GITHUB_STEP_SUMMARY. Gitea has no commit-comments API (confirmed
in issue #75), so the gh call always failed. GITHUB_STEP_SUMMARY works
on both GitHub Actions and Gitea Actions as the workflow-run summary
page, which is the natural place for post-deploy action items.
- Removed now-unnecessary GH_TOKEN env var and contents:write permission.
2. check-merge-group-trigger.yml:
- Converted to no-op stub. Gitea has no merge queue feature and no
merge_group: event type, so this workflow's lint would find nothing
to verify (all workflows vacuously pass). Keeping workflow+job name
unchanged preserves commit-status context names for branch protection
consumers. Dropped the merge_group: trigger since it would never fire
on Gitea. Dropped the full bash linter + gh api call.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Target handle (top of card): Enter/Space extracts this node from
its parent, moving it to the root level.
Source handle (bottom of card): Enter/Space nests the currently
selected node as a child of this node (requires another node to be
selected first).
Both handles gain tabIndex=0, role="button", a descriptive aria-label,
and a blue focus ring so keyboard-only users can navigate the
workspace hierarchy without a mouse. Uses the existing nestNode store
action — no new API surface needed.
Updates the canvas audit doc to mark the LOW edge-anchor item done.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Update go.mod require and main.go import to use the Gitea-hosted
module path go.moleculesai.app/plugin/gh-identity (migrated from
github.com/Molecule-AI/molecule-ai-plugin-gh-identity).
Follows the pattern of the org-template URL migrations (github.com ->
git.moleculesai.app) applied to Go module imports.
Fixes molecule-core#91.
Ref: molecule-internal#71.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PUT /workspaces/:id/files and DELETE /workspaces/:id/files updated the
config volume but never restarted the container, so the running agent
continued serving stale file content from its in-memory cache. The
SecretsHandler already had this pattern (issue #15); TemplatesHandler
was missing it.
Fix: after every successful write/delete in WriteFile, DeleteFile, and
ReplaceFiles, call h.wh.RestartByID(workspaceID) asynchronously, guarded
by h.wh != nil (nil-tolerant for callers that only use read-only
surfaces). The RestartByID coalescing gate prevents thundering-herd on
concurrent requests.
Fixes#151.
Fixes#87 (duplicate effort closed — core-be also filed #183).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The "Node Rendering" and "Drag and Drop" sections still said
"mouse only, no keyboard alternative" and "Keyboard alternative: None"
despite PR #182 (Arrow keys) being merged. Update both to reflect
the keyboard-accessible node drag.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Issue #86: TestStartSweeper_RecordsMetricsOnSuccess fails in full-suite.
Root cause: two cooperating bugs in the sweeper test harness.
1. Sweeper loop called sweepOnce after ctx cancellation (double-increment).
When ctx was cancelled the loop's select received ctx.Done(), called
sweepOnce with the cancelled ctx, storage.Sweep returned context error,
and metrics.PendingUploadsSweepError() incremented the error counter a
SECOND time before the loop exited. Subsequent tests captured a polluted
error baseline and their deltaError assertions failed.
2. Tests called defer cancel() without waiting for the goroutine to exit.
The goroutine could still be blocked on Sweep (waiting for the next
ticker's C channel) when the next test called metricDelta(). If the
goroutine's Sweep returned during the next test's measurement window,
the shared metric counters mutated mid-baseline.
Fix (production code):
- Guard the ticker arm: if ctx.Err() != nil, continue instead of calling
sweepOnce. This prevents the post-cancellation sweep from running.
Fix (test harness):
- startSweeperWithInterval gains a done chan struct{} parameter. When the
loop exits the channel is closed exactly once.
- StartSweeperForTest starts the goroutine and returns the done channel,
allowing tests to drain it with <-done after cancel() — guaranteeing
the goroutine has fully terminated before the next test's baseline.
All 8 sweeper tests now use StartSweeperForTest and drain the done
channel before returning, ensuring stable metric baselines across the
full suite.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
os.Chmod(dst, 0o555) silently passes when os.Geteuid() == 0 because
root bypasses POSIX permission checks. A previous attempt to use a
symlink to /dev/full also fails: Go's os.MkdirAll resolves the symlink
during path traversal and the kernel allows mkdir("/dev/full") as a
device-table entry — io.Copy to /dev/full then succeeds with 0 bytes
written and returns nil.
The honest, consistent fix mirrors TestLocalResolver_CopyFileSourceUnreadable:
skip when running as root. The write-failure propagation logic is
exercised correctly in non-root CI environments.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(plugins/test): skip TestLocalResolver_BubblesUpCopyFailure when running as root
Fixes issue #87: the test sets chmod(dst, 0o555) to make the
destination read-only and asserts the copy fails. On Linux, root
bypasses filesystem permissions and can write to 0o555 directories,
so the copy succeeds when running as root and the assertion fails.
Fix: check os.Getuid() == 0 at the start of the test and skip with
a clear message. Mirrors the existing skip in
TestLocalResolver_CopyFileSourceUnreadable (line 175) which already
handles the same root-bypass issue for unreadable source files.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes canvas audit item: MEDIUM keyboard-accessible node drag.
- Arrow keys move the selected node by 10px per press; Shift+Arrow
moves by 50px. Position is persisted to the backend via savePosition.
- The modal-dialog guard (same pattern as ? shortcut) prevents Arrow
keys from moving nodes when a modal like KeyboardShortcutsDialog is
open — dialogs own their own arrow semantics.
- All shortcuts guarded by the inInput check so Arrow keys still work
for text navigation inside inputs/textareas.
Changes:
- canvas.ts: new moveNode(dx, dy) store action — updates position
directly without the grow-parents pass that onNodesChange runs on
every drag tick (avoids edge-chase flicker).
- useKeyboardShortcuts.ts: Arrow key handler added.
- canvas.test.ts: new moveNode unit tests (position update, no-op,
savePosition call).
- useKeyboardShortcuts.test.tsx: new integration tests for all
keyboard shortcuts including the new Arrow key handlers.
- canvas-audit-items.md: Keyboard Shortcuts section upgraded to ✅,
drag item marked done.
- canvas-events.test.ts: fix pre-existing double-}); syntax error.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(tests): clear platform_auth cache before each test
Fixes issue #160: workspace tests fail when MOLECULE_WORKSPACE_TOKEN
is set in the environment.
The bug: platform_auth._cached_token is populated at module import or
first get_token() call and persists for the process lifetime. Tests
that use monkeypatch.delenv("MOLECULE_WORKSPACE_TOKEN") to simulate "no
token in env" were failing because delenv removes the env var but not
the module-level cache — subsequent get_token() calls returned the
stale cached value.
Fix: add a function-scoped autouse fixture in conftest.py that calls
platform_auth.clear_cache() before every test. The import is inside the
fixture to avoid collection-time import issues when platform_auth is
not yet available.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>