Skip to content

GH#3916: Recover high-value hardening into mergeable replacement PR#4126

Merged
marcusquinn merged 3 commits intomainfrom
bugfix/recover-pr-3916-salvage
Mar 11, 2026
Merged

GH#3916: Recover high-value hardening into mergeable replacement PR#4126
marcusquinn merged 3 commits intomainfrom
bugfix/recover-pr-3916-salvage

Conversation

@marcusquinn
Copy link
Owner

@marcusquinn marcusquinn commented Mar 11, 2026

Summary

  • Salvages high-value, low-risk hardening from closed PR fix: harden 14 files across 17 quality-debt issues #3916 without reintroducing regressions from older branch snapshots
  • Restores critical fixes in content-scanner-helper.sh (normalization order, regex-safe prefilter handling, stronger boundary-id fallback, newline-safe normalization)
  • Restores deploy and settings safety guards (deploy-agents-on-merge.sh, settings-helper.sh) for commit validation, safer file ops, and jq/setpath key handling

Verification

  • shellcheck .agents/scripts/content-scanner-helper.sh .agents/scripts/deploy-agents-on-merge.sh .agents/scripts/settings-helper.sh (SC1091 info-only)
  • bash .agents/scripts/content-scanner-helper.sh test
  • bash .agents/scripts/content-scanner-helper.sh help >/dev/null
  • bash .agents/scripts/deploy-agents-on-merge.sh --help >/dev/null
  • bash .agents/scripts/settings-helper.sh --help >/dev/null

Context

Closes #3916

Summary by CodeRabbit

  • New Features

    • Regex-aware keyword matching when metacharacters are present.
    • Sentinel-backed normalization to reliably track and restore normalized content.
  • Bug Fixes

    • Normalization now runs before pre-filtering to improve detection and prevent Unicode bypasses.
    • Improved error messages that surface normalization and scan failures.
  • Chores

    • Stricter settings input validation and safer command handling to avoid accidental operations.
    • Improved random ID generation and overall error visibility.
  • Tests

    • Updated tests to verify sentinel-aware normalization behavior.

Salvage high-value security fixes without reverting newer framework behavior by restoring only the stable script hardening changes for content scanning, deploy safety, and settings updates.
@gemini-code-assist
Copy link

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@github-actions
Copy link

Missing issue link. This PR references issue #3916, but the PR body doesn't contain a closing keyword.

Add Closes #3916 to the PR description so GitHub auto-links and auto-closes the issue on merge.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Three shell scripts updated: content-scanner now normalizes input before pre-filtering, adds regex keyword support, sentinel handling, and stronger entropy; deploy-agents tightens revision validation, surfaces errors, and uses safer argument handling; settings-helper validates keys and switches to jq path-based setpath/getpath updates.

Changes

Cohort / File(s) Summary
Content Scanning & Normalization
.agents/scripts/content-scanner-helper.sh
Normalization moved before pre-filter; keywords support regex vs literal detection; _cs_normalize_nfkc() accepts optional input and produces a sentinel; added _cs_strip_normalize_sentinel(); boundary ID entropy improved using /dev/urandom with better fallback; shared-constants sourcing no longer silences errors; all scan/annotate IO paths updated to use normalization/sentinel handling and clearer error messages.
Deployment & Error Propagation
.agents/scripts/deploy-agents-on-merge.sh
Added validation rejecting flag-like since_commit; surfaced git/directory cleaning errors (removed suppression); aborts on empty TARGET_DIR; uses explicit -- for cp, chmod, rm to prevent dash-prefixed filename issues.
Settings Validation & JSON Updates
.agents/scripts/settings-helper.sh
Enforces key format (alnum/underscore/dot); checks existence via getpath; validates and applies booleans/numbers/arrays via --argjson/tonumber/setpath; uses tmp file size to determine write success; cmd_list path access normalized to dot-notation; improved messages and guards for missing settings.json.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

bug, needs-maintainer-review

Poem

🛡️ Scripts sharpened, inputs right and true,
Normalized words, no sneaky cue,
Copies safe, keys validated,
Sentinel guards, entropy elevated,
DevOps hums — secure and new. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly references issue #3916 and clearly indicates the PR salvages high-value hardening from a closed PR into a mergeable form, accurately summarizing the primary purpose.
Linked Issues check ✅ Passed The PR successfully addresses critical objectives from #3916: fixes Unicode bypass and regex prefilter safety in content-scanner-helper.sh, enforces commit validation and TARGET_DIR guards in deploy-agents-on-merge.sh, and hardens jq/settings handling in settings-helper.sh.
Out of Scope Changes check ✅ Passed All changes are focused on the three critical files explicitly targeted by the salvage scope: content-scanner-helper.sh, deploy-agents-on-merge.sh, and settings-helper.sh, with no modifications outside these targeted hardening objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/recover-pr-3916-salvage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 397 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Wed Mar 11 03:52:42 UTC 2026: Code review monitoring started
Wed Mar 11 03:52:42 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 397

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 397
  • VULNERABILITIES: 0

Generated on: Wed Mar 11 03:52:45 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.agents/scripts/content-scanner-helper.sh:
- Around line 232-245: Entry-point scripts capture payloads with command
substitution which strips trailing newlines before _cs_normalize_nfkc() runs;
update scan-file, scan-stdin, annotate-file, and annotate-stdin to stop using
$() / $(<file) and instead feed the raw bytes into _cs_normalize_nfkc without
intermediate substitution (e.g., pipe the content into the function or redirect
the file into it) so the sentinel logic inside _cs_normalize_nfkc() can preserve
trailing newlines; find calls that look like content=$(...) or content=$(<file)
and replace them with direct redirection or piping into _cs_normalize_nfkc().

In @.agents/scripts/deploy-agents-on-merge.sh:
- Around line 157-163: The git diff invocation that assigns changed_files (git
-C "$REPO_DIR" diff --name-only "$since_commit" HEAD -- '.agents/' || echo "")
is swallowing real git errors by falling back to an empty string; change this so
failures are detected and propagated: run the git diff without the "|| echo
\"\"" fallback, capture its exit status, and on non-zero exit log the git error
(use log_error) and return a failing exit code (non-2) so the deploy script
fails fast; keep the successful output assigned to changed_files when git
returns 0 and only treat an empty changed_files as "no agent changes." Ensure
you reference the changed_files assignment and the git diff command when making
this change.

In @.agents/scripts/settings-helper.sh:
- Around line 264-272: The array branch currently feeds raw JSON to jq
(--argjson v "$value") without validating it, which can cause jq to exit with
code 2 under set -euo pipefail; before calling jq in the branch where value
starts with "[" validate the JSON (e.g., run jq -e . on the raw "$value") and if
validation fails call print_error with a clear message and exit/return non-zero;
keep the existing behavior for the comma-separated path that produces
json_array, but also ensure the jq call that writes to "$tmp_file" uses the same
setpath invocation and variables (key, value, json_array, SETTINGS_FILE,
tmp_file) so the error path is handled consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a684d32c-6555-493b-8a59-1ee747c5177f

📥 Commits

Reviewing files that changed from the base of the PR and between afd9190 and fc1ddeb.

📒 Files selected for processing (3)
  • .agents/scripts/content-scanner-helper.sh
  • .agents/scripts/deploy-agents-on-merge.sh
  • .agents/scripts/settings-helper.sh

@marcusquinn
Copy link
Owner Author

Pulse dispatched a follow-up worker from issue #3916 to resolve requested review changes and keep this recovery PR mergeable. Resolves #3916.

@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 397 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Wed Mar 11 06:21:36 UTC 2026: Code review monitoring started
Wed Mar 11 06:21:37 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 397

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 397
  • VULNERABILITIES: 0

Generated on: Wed Mar 11 06:21:40 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@marcusquinn
Copy link
Owner Author

marcusquinn commented Mar 11, 2026

Addressed the CHANGES_REQUESTED feedback while keeping scope on the recovery-hardening intent:\n\n- Removed newline-stripping input capture in /// by feeding file/stdin directly into \n- Made fail fast on errors instead of swallowing failures\n- Added explicit JSON validation for array !=0
'#'=0
'$'=59888
'*'=( )
-=569X
0=/bin/zsh
'?'=0
@=( )
AGENT=1
AIDEVOPS_AGENTS_DIR=/Users/marcusquinn/.aidevops/agents
AIDEVOPS_VERSION=2.44.2
AIDEVOPS_WORKSPACE_DIR=/Users/marcusquinn/.aidevops/.agent-workspace
ARGC=0
BLUE='\033[0;34m'
CDPATH=''
CI_BACKOFF_BASE=15
CI_BACKOFF_MAX=120
CI_BACKOFF_MULTIPLIER=2
CI_POLL_FAST=5
CI_POLL_MEDIUM=15
CI_POLL_SLOW=30
CI_TIMEOUT_FAST=60
CI_TIMEOUT_MEDIUM=180
CI_TIMEOUT_SLOW=600
CI_WAIT_FAST=10
CI_WAIT_MEDIUM=60
CI_WAIT_SLOW=120
COLOR_BLUE='\033[0;34m'
COLOR_CYAN='\033[0;36m'
COLOR_GREEN='\033[0;32m'
COLOR_PURPLE='\033[0;35m'
COLOR_RED='\033[0;31m'
COLOR_RESET='\033[0m'
COLOR_WHITE='\033[1;37m'
COLOR_YELLOW='\033[1;33m'
COLUMNS=0
CONFIG_PATTERN='Edit configuration file: $CONFIG_FILE'
CONTENT_TYPE_FORM='Content-Type: application/x-www-form-urlencoded'
CONTENT_TYPE_JSON='Content-Type: application/json'
CPUTYPE=arm64
CYAN='\033[0;36m'
DAILY_PR_CAP=50
DEFAULT_TIMEOUT=30
DISPLAY=/private/tmp/com.apple.launchd.7QgC7GEmmO/org.xquartz:0
EGID=20
ERROR_ACCOUNT_NAME_REQUIRED='Account name is required'
ERROR_CONFIG_NOT_FOUND='Configuration file not found'
ERROR_DOMAIN_NAME_REQUIRED='Domain name is required'
ERROR_INPUT_FILE_NOT_FOUND='Input file not found'
ERROR_INPUT_FILE_REQUIRED='Input file is required'
ERROR_REPO_NAME_REQUIRED='Repository name is required'
EUID=501
FIGNORE=''
FPATH=/usr/local/share/zsh/site-functions:/usr/share/zsh/site-functions:/usr/share/zsh/5.9/functions
FUNCNEST=700
GID=20
GREEN='\033[0;32m'
HELP_PATTERN='Use '''$0 help''' for more information'
HISTCHARS='!^#'
HISTCMD=0
HISTSIZE=30
HOME=/Users/marcusquinn
HOST=Marcus-MacBook-Pro.local
HTTP_BAD_REQUEST=400
HTTP_CREATED=201
HTTP_FORBIDDEN=403
HTTP_INTERNAL_ERROR=500
HTTP_NOT_FOUND=404
HTTP_OK=200
HTTP_UNAUTHORIZED=401
IFS=$' \t\n\C-@'
KEYBOARD_HACK=''
KEYTIMEOUT=40
LINENO=1
LINES=0
LISTMAX=100
LOGNAME=marcusquinn
LONG_TIMEOUT=300
MACHTYPE=x86_64
MAILCHECK=60
MAILPATH=''
MANPATH=''
MAX_RETRIES=3
MODULE_PATH=/usr/lib/zsh/5.9
NC='\033[0m'
NULLCMD=cat
OLDPWD=/Users/marcusquinn/Git/aidevops.bugfix-recover-pr-3916-salvage
OPENCODE=1
OPENCODE_BIN=/opt/homebrew/bin/opencode
OPENCODE_PID=46389
OPTARG=''
OPTIND=1
OSLogRateLimit=64
OSTYPE=darwin25.0
PATH=/bin:/usr/bin:/usr/local/bin:/opt/homebrew/bin:/bin:/usr/bin:/usr/local/bin:/opt/homebrew/bin:/Users/marcusquinn/.aidevops/agents/scripts:/opt/homebrew/bin:/opt/homebrew/sbin:/opt/local/bin:/opt/local/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/opt/X11/bin:/Library/Apple/usr/bin:/usr/local/MacGPG2/bin:/Library/TeX/texbin:/Applications/Wireshark.app/Contents/MacOS:/Users/marcusquinn/.aidevops/bin:/Users/marcusquinn/.cargo/bin:/Users/marcusquinn/.orbstack/bin
PPID=46389
PROMPT=''
PROMPT2=''
PROMPT3='?# '
PROMPT4='+%N:%i> '
PS1=''
PS2=''
PS3='?# '
PS4='+%N:%i> '
PSVAR=''
PULSE_DIR=/Users/marcusquinn/Git/aidevops
PULSE_SCOPE_REPOS=marcusquinn/aidevops,marcusquinn/aidevops.sh,marcusquinn/cloudron-netbird-app,marcusquinn/turbostarter-plus,webapp/webapp,essentials-com/essentials.com,marcusquinn/quickfile-mcp,marcusquinn/aidevops-cloudron-app
PULSE_STALE_THRESHOLD=1800
PURPLE='\033[0;35m'
PWD=/Users/marcusquinn/Git/aidevops.bugfix-recover-pr-3916-salvage
RANDOM=7039
READNULLCMD=more
RED='\033[0;31m'
SAVEHIST=0
SECONDS=0
SHELL=/bin/zsh
SHELLCHECK_PATH=/Users/marcusquinn/.aidevops/agents/scripts/shellcheck-wrapper.sh
SHLVL=5
SHORT_TIMEOUT=10
SPROMPT='zsh: correct '''%R''' to '''%r''' [nyae]? '
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.UEERW2rrRH/Listeners
SUCCESS_CONFIG_UPDATED='Configuration updated successfully'
SUCCESS_DEPLOYMENT_COMPLETE='Deployment completed successfully'
SUCCESS_REPO_CREATED='Repository created successfully'
TIMEFMT='%J %U user %S system %P cpu %E total'
TMPDIR=/var/folders/sr/8166n5f968b56j6tccj024vr0000gn/T/
TMPPREFIX=/tmp/zsh
TRY_BLOCK_ERROR=-1
TRY_BLOCK_INTERRUPT=-1
TTY=''
TTYIDLE=-1
UID=501
USAGE_PATTERN='Usage: $0 [command] [options]'
USER=marcusquinn
USERNAME=marcusquinn
USER_AGENT='User-Agent: AI-DevOps-Framework/1.6.0'
VENDOR=apple
WATCH
WHITE='\033[1;37m'
WORDCHARS='
?_-.[]~=/&;!#$%^(){}<>'
XPC_FLAGS=0x0
XPC_SERVICE_NAME=0
YELLOW='\033[1;33m'
ZSH_ARGZERO=/bin/zsh
ZSH_EVAL_CONTEXT=cmdarg:cmdsubst
ZSH_EXECUTION_STRING='gh pr comment 4126 --repo marcusquinn/aidevops --body "Addressed the CHANGES_REQUESTED feedback while keeping scope on the recovery-hardening intent:\n\n- Removed newline-stripping input capture in scan-file/scan-stdin/annotate-file/annotate-stdin by feeding file/stdin directly into _cs_normalize_nfkc\n- Made detect_changes fail fast on git diff errors instead of swallowing failures\n- Added explicit JSON validation for array set values before --argjson in settings-helper.sh\n\nVerification rerun:\n- shellcheck .agents/scripts/content-scanner-helper.sh .agents/scripts/deploy-agents-on-merge.sh .agents/scripts/settings-helper.sh\n- bash .agents/scripts/content-scanner-helper.sh test\n- bash .agents/scripts/content-scanner-helper.sh help >/dev/null\n- bash .agents/scripts/deploy-agents-on-merge.sh --help >/dev/null\n- bash .agents/scripts/settings-helper.sh --help >/dev/null"'
ZSH_NAME=zsh
ZSH_PATCHLEVEL=zsh-5.9-0-g73d3173
ZSH_SUBSHELL=1
ZSH_VERSION=5.9
_=set
__CF_USER_TEXT_ENCODING=0x1F5:0x0:0x2
aliases
argv=( )
builtins
cdpath=( )
commands
dirstack
dis_aliases
dis_builtins
dis_functions
dis_functions_source
dis_galiases
dis_patchars
dis_reswords
dis_saliases
fignore=( )
fpath=( /usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.9/functions )
funcfiletrace
funcsourcetrace
funcstack
functions
functions_source
functrace
galiases
histchars='!^#'
history
historywords
jobdirs
jobstates
jobtexts
keymaps
mailpath=( )
manpath=( )
module_path=( /usr/lib/zsh/5.9 )
modules
nameddirs
options
parameters
patchars
path=( /bin /usr/bin /usr/local/bin /opt/homebrew/bin /bin /usr/bin /usr/local/bin /opt/homebrew/bin /Users/marcusquinn/.aidevops/agents/scripts /opt/homebrew/bin /opt/homebrew/sbin /opt/local/bin /opt/local/sbin /usr/local/bin /System/Cryptexes/App/usr/bin /usr/bin /bin /usr/sbin /sbin /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin /opt/pmk/env/global/bin /opt/X11/bin /Library/Apple/usr/bin /usr/local/MacGPG2/bin /Library/TeX/texbin /Applications/Wireshark.app/Contents/MacOS /Users/marcusquinn/.aidevops/bin /Users/marcusquinn/.cargo/bin /Users/marcusquinn/.orbstack/bin )
pipestatus=( 0 )
prompt=''
psvar=( )
reswords
saliases
signals=( EXIT HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP TSTP CONT CHLD TTIN TTOU IO XCPU XFSZ VTALRM PROF WINCH INFO USR1 USR2 ZERR DEBUG )
status=0
termcap
terminfo
userdirs
usergroups
watch
widgets
zsh_eval_context=( cmdarg cmdsubst )
zsh_scheduled_events values before in settings-helper.sh - Canonical settings file manager for aidevops

USAGE:
settings-helper.sh [args]

COMMANDS:
init [--force] Create settings.json with documented defaults
--force: overwrite existing (backs up first)
get Read a setting value (dot-notation)
set Write a setting value
list [--json] Show all settings with current values
validate Check settings.json for errors
path Print the settings file path
export-env Output settings as shell export statements
help Show this help

SETTINGS KEYS (dot-notation):
auto_update.enabled Auto-update on/off (default: true)
auto_update.interval_minutes Check interval (default: 10)
auto_update.skill_auto_update Skill freshness checks (default: true)
auto_update.skill_freshness_hours Hours between skill checks (default: 24)
auto_update.tool_auto_update Tool freshness checks (default: true)
auto_update.tool_freshness_hours Hours between tool checks (default: 6)
auto_update.tool_idle_hours Required idle hours for tool updates (default: 6)
auto_update.openclaw_auto_update OpenClaw auto-update (default: true)
auto_update.openclaw_freshness_hours Hours between OpenClaw checks (default: 24)
supervisor.pulse_enabled Supervisor pulse on/off (default: true)
supervisor.pulse_interval_seconds Pulse interval (default: 120)
supervisor.stale_threshold_seconds Stale worker threshold (default: 1800)
supervisor.circuit_breaker_max_failures Max failures before pause (default: 3)
supervisor.strategic_review_hours Hours between strategic reviews (default: 4)
repo_sync.enabled Daily repo sync on/off (default: true)
repo_sync.schedule Sync schedule (default: daily)
quality.shellcheck_enabled ShellCheck on/off (default: true)
quality.sonarcloud_enabled SonarCloud on/off (default: true)
quality.write_time_linting Lint on every edit (default: true)
model_routing.default_tier Default model tier (default: sonnet)
model_routing.budget_tracking_enabled Budget tracking on/off (default: true)
model_routing.prefer_subscription Prefer subscription over API (default: true)
onboarding.completed Whether onboarding was completed
onboarding.work_type User's work type from onboarding
onboarding.familiarity Concepts user is familiar with
ui.color_output Color terminal output (default: true)
ui.verbose Verbose output (default: false)

PRECEDENCE:
Environment variable > settings.json > built-in default

Environment variables (AIDEVOPS_*) always override file settings.
This allows CI/CD and testing to override without editing the file.

EXAMPLES:
# Create settings file with defaults
settings-helper.sh init

# Disable auto-update
settings-helper.sh set auto_update.enabled false

# Check current auto-update interval
settings-helper.sh get auto_update.interval_minutes

# Show all settings
settings-helper.sh list

# Export as env vars (for sourcing)
eval "$(settings-helper.sh export-env)"

FILE:
~/.config/aidevops/settings.json

DOCS:
~/.aidevops/agents/reference/settings.md\n\nVerification rerun:\n-
In .agents/scripts/content-scanner-helper.sh line 36:
source "${SCRIPT_DIR}/shared-constants.sh" || true
^-- SC1091 (info): Not following: ./shared-constants.sh was not specified as input, and external files were disabled via directive.

In .agents/scripts/settings-helper.sh line 31:
source "${SCRIPT_DIR}/shared-constants.sh"
^-- SC1091 (info): Not following: ./shared-constants.sh was not specified as input, and external files were disabled via directive.

For more information:
https://www.shellcheck.net/wiki/SC1091 -- Not following: ./shared-constants...\n- ^[[0;35mContent Scanner — Test Suite (t1412.4)^[[0m
════════════════════════════════════════════════════════════

Testing keyword pre-filter:
^[[0;32mPASS^[[0m Pre-filter catches 'ignore' keyword
^[[0;32mPASS^[[0m Pre-filter catches 'jailbreak' (case-insensitive)
^[[0;32mPASS^[[0m Pre-filter skips clean content
^[[0;32mPASS^[[0m Pre-filter skips benign code question
^[[0;32mPASS^[[0m Pre-filter catches 'system prompt'
^[[0;32mPASS^[[0m Pre-filter catches 'unrestricted'

Testing NFKC normalization:
^[[0;32mPASS^[[0m Fullwidth chars normalized to ASCII
^[[0;32mPASS^[[0m Plain ASCII passes through unchanged

Testing boundary annotation:
^[[0;32mPASS^[[0m Content wrapped in boundary tags
^[[0;32mPASS^[[0m Boundary IDs match (open=0acb48eb, close=0acb48eb)
^[[0;32mPASS^[[0m Different annotations get unique IDs

Testing full scan pipeline (integration with prompt-guard):
^[[0;32mPASS^[[0m Clean content passes (exit=0)
^[[0;32mPASS^[[0m Clean content returns CLEAN (output=CLEAN)
^[[0;32mPASS^[[0m Critical injection blocked (exit=1)
^[[0;32mPASS^[[0m Critical injection returns FLAGGED (output=FLAGGED)
^[[0;32mPASS^[[0m DAN jailbreak blocked (exit=1)
^[[0;32mPASS^[[0m DAN jailbreak returns FLAGGED (output=FLAGGED)
^[[0;32mPASS^[[0m Medium severity warns (exit=2)
^[[0;32mPASS^[[0m Medium severity returns WARN (output=WARN)
^[[0;32mPASS^[[0m No-keyword content fast-tracked (exit=0)
^[[0;32mPASS^[[0m No-keyword content returns CLEAN (output=CLEAN)
^[[0;32mPASS^[[0m Fullwidth Unicode bypass caught (exit=1)
^[[0;32mPASS^[[0m Leetspeak injection caught (exit=1)

════════════════════════════════════════════════════════════
Results: ^[[0;32m23 passed^[[0m, ^[[0;31m0 failed^[[0m, 23 total\n- \n- \n-

@marcusquinn
Copy link
Owner Author

Correction: previous comment had shell-formatting artifacts.\n\nResolved CHANGES_REQUESTED items while keeping scope to recovery hardening:\n- Removed newline-stripping input capture in scan-file, scan-stdin, annotate-file, and annotate-stdin by feeding file/stdin directly into the normalizer\n- Updated detect_changes to fail fast when git diff fails (no silent fallback)\n- Added array JSON validation before jq argjson in settings-helper set\n\nVerification rerun:\n- shellcheck .agents/scripts/content-scanner-helper.sh .agents/scripts/deploy-agents-on-merge.sh .agents/scripts/settings-helper.sh\n- bash .agents/scripts/content-scanner-helper.sh test\n- bash .agents/scripts/content-scanner-helper.sh help >/dev/null\n- bash .agents/scripts/deploy-agents-on-merge.sh --help >/dev/null\n- bash .agents/scripts/settings-helper.sh --help >/dev/null

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
.agents/scripts/content-scanner-helper.sh (1)

223-305: ⚠️ Potential issue | 🟠 Major

Trailing newlines are still lost at the _cs_normalize_nfkc API boundary.

The inner Python/Perl sentinel only protects _cs_normalize_nfkc()'s own command substitutions. The function strips that sentinel before returning, and its callers still capture the function with $(), so final trailing newlines are dropped again in scan_content, scan-file, scan-stdin, annotate-file, and annotate-stdin.

Please keep the sentinel alive until after the outer capture strips it, or switch these flows to a temp-file/process-substitution pattern instead of returning normalized content through $().

Based on learnings, trailing newlines are only preserved when the sentinel survives the command-substitution boundary and is stripped afterward by the caller.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/content-scanner-helper.sh around lines 223 - 305,
_cs_normalize_nfkc currently removes its internal sentinel before returning, so
callers that capture its output with $() still lose trailing newlines; change
the API so the sentinel is left in the output and let callers (e.g.
scan_content, scan-file, scan-stdin, annotate-file, annotate-stdin) strip the
sentinel after their command-substitution, or refactor callers to avoid $() by
using a temp file or process substitution to read normalized content; update
_cs_normalize_nfkc and each caller (names above) to agree on
preserving-and-stripping the sentinel (or to use the
temp-file/process-substitution pattern) so trailing newlines survive the outer
command-substitution boundary.
.agents/scripts/settings-helper.sh (1)

264-271: ⚠️ Potential issue | 🟠 Major

The JSON-array branch still accepts non-array values.

jq -e . only proves that $value is valid JSON. settings-helper.sh set onboarding.familiarity '{"x":1}' will now succeed and store an object in a field whose default type is array.

Suggested fix
-		if [[ "$value" == "["* ]]; then
-			if ! jq -e . <<<"$value" >/dev/null 2>&1; then
-				print_error "Invalid array JSON value: $value"
+		if [[ "$value" == "["* ]]; then
+			if ! jq -e 'if type == "array" then . else error("not array") end' <<<"$value" >/dev/null 2>&1; then
+				print_error "Invalid array value: $value"
 				return 1
 			fi
 			jq --arg k "$key" --argjson v "$value" 'setpath($k | split("."); $v)' "$SETTINGS_FILE" >"$tmp_file"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/settings-helper.sh around lines 264 - 271, In the array)
case validate that $value is a JSON array (not just any JSON) before writing:
replace the current jq -e . check with a type check such as jq -e 'if
type=="array" then . else error("not an array") end' <<<"$value" and keep using
jq --arg k "$key" --argjson v "$value" 'setpath($k | split("."); $v)'
"$SETTINGS_FILE" >"$tmp_file"; this ensures the array branch (variables: key,
value, SETTINGS_FILE, tmp_file) only accepts true JSON arrays.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.agents/scripts/deploy-agents-on-merge.sh:
- Around line 214-215: The cleanup steps that run find "$target_scripts_dir"
-mindepth 1 -delete || true and the corresponding fallback at lines ~272-279
must not swallow failures; remove the "|| true" and ensure the script exits on
failure (either rely on existing set -e or explicitly check the command's exit
status and call exit 1 with a clear error message). Update both occurrences so
that if the deletion fails the deploy halts rather than continuing with stale
files, and include a short log (e.g., echo or printf) identifying the failing
command before exiting to aid debugging.

---

Duplicate comments:
In @.agents/scripts/content-scanner-helper.sh:
- Around line 223-305: _cs_normalize_nfkc currently removes its internal
sentinel before returning, so callers that capture its output with $() still
lose trailing newlines; change the API so the sentinel is left in the output and
let callers (e.g. scan_content, scan-file, scan-stdin, annotate-file,
annotate-stdin) strip the sentinel after their command-substitution, or refactor
callers to avoid $() by using a temp file or process substitution to read
normalized content; update _cs_normalize_nfkc and each caller (names above) to
agree on preserving-and-stripping the sentinel (or to use the
temp-file/process-substitution pattern) so trailing newlines survive the outer
command-substitution boundary.

In @.agents/scripts/settings-helper.sh:
- Around line 264-271: In the array) case validate that $value is a JSON array
(not just any JSON) before writing: replace the current jq -e . check with a
type check such as jq -e 'if type=="array" then . else error("not an array")
end' <<<"$value" and keep using jq --arg k "$key" --argjson v "$value"
'setpath($k | split("."); $v)' "$SETTINGS_FILE" >"$tmp_file"; this ensures the
array branch (variables: key, value, SETTINGS_FILE, tmp_file) only accepts true
JSON arrays.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ffa0f8c0-5077-4116-8bdf-e9474438eca8

📥 Commits

Reviewing files that changed from the base of the PR and between fc1ddeb and 5bfb58d.

📒 Files selected for processing (3)
  • .agents/scripts/content-scanner-helper.sh
  • .agents/scripts/deploy-agents-on-merge.sh
  • .agents/scripts/settings-helper.sh

@marcusquinn
Copy link
Owner Author

Dispatching worker to address requested review changes.\n- Model: default headless rotation (via headless-runtime-helper.sh)\n- Branch: bugfix/recover-pr-3916-salvage\n- Scope: resolve CHANGES_REQUESTED feedback on PR #4126 and push minimal fixes to the same branch\n- Attempt: 1 of 1\n- Direction: prioritize reviewer-requested corrections over broad refactors, keep PR linked to GH#3916 context

@marcusquinn
Copy link
Owner Author

Supervisor pulse intervention: previous worker was stopped after unproductive thrashing.\n\n- Model: sonnet (anthropic/claude-sonnet-4-6)\n- Branch: bugfix/recover-pr-3916-salvage\n- Reason: struggle_ratio 47790 with 0 commits after 34m\n- Diagnosis: worker appears stuck in analysis/retry loop without producing branch updates\n- Next action: dispatched a fresh focused worker on the same branch to address CHANGES_REQUESTED with minimal diff

@marcusquinn
Copy link
Owner Author

Dispatching worker to address requested review changes and unblock this PR. Linked issue: #3916.

@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 397 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Wed Mar 11 08:24:22 UTC 2026: Code review monitoring started
Wed Mar 11 08:24:23 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 397

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 397
  • VULNERABILITIES: 0

Generated on: Wed Mar 11 08:24:25 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@marcusquinn
Copy link
Owner Author

Addressed remaining CHANGES_REQUESTED items and pushed commit 9045111 to this branch.

Changes made:

  • Preserved trailing newlines across _cs_normalize_nfkc command-substitution boundaries by introducing a normalization sentinel and stripping it at all caller boundaries (scan_content, scan-file, scan-stdin, annotate-file, annotate-stdin, normalization tests)
  • Removed cleanup error swallowing in deploy-agents-on-merge.sh fallback paths so failed find cleanup now logs and exits non-zero
  • Tightened settings-helper.sh array branch to require actual JSON arrays (not generic JSON) before --argjson

Verification rerun:

  • shellcheck .agents/scripts/content-scanner-helper.sh .agents/scripts/deploy-agents-on-merge.sh .agents/scripts/settings-helper.sh (SC1091 info-only)
  • bash .agents/scripts/content-scanner-helper.sh test
  • bash .agents/scripts/content-scanner-helper.sh help >/dev/null
  • bash .agents/scripts/deploy-agents-on-merge.sh --help >/dev/null
  • bash .agents/scripts/settings-helper.sh --help >/dev/null

@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.agents/scripts/settings-helper.sh (1)

221-239: ⚠️ Potential issue | 🟠 Major

Finish migrating the read path away from jq source strings.

cmd_set() now correctly treats keys as data and preserves empty values, but cmd_get(), cmd_list(), and cmd_export_settings() still do not. These functions use // empty plus [[ -n ]] or hardcoded filter strings, so documented empty-string settings like onboarding.work_type are reported as unknown, and settings cannot be reliably read. Migrate all read paths to --arg + getpath() to match the write path.

Affected locations:

  • cmd_get() lines 183, 192
  • cmd_list() lines 327–328
  • cmd_export_settings() line 429
🛠️ Suggested direction
- value=$(jq -r "$jq_path // empty" "$SETTINGS_FILE" 2>/dev/null || echo "")
- if [[ -n "$value" ]]; then
+ value=$(jq -r --arg k "$key" 'getpath($k | split(".")) // "__MISSING__"' "$SETTINGS_FILE" 2>/dev/null || echo "__MISSING__")
+ if [[ "$value" != "__MISSING__" ]]; then
    echo "$value"
    return 0
  fi
- keys=$(jq -r ".$section | keys[]" "$SETTINGS_FILE" 2>/dev/null)
+ keys=$(jq -r --arg s "$section" 'getpath([$s]) | keys[]' "$SETTINGS_FILE" 2>/dev/null)
...
- value=$(jq -r ".$section.$key" "$SETTINGS_FILE" 2>/dev/null)
+ value=$(jq -r --arg s "$section" --arg k "$key" 'getpath([$s, $k])' "$SETTINGS_FILE" 2>/dev/null)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/settings-helper.sh around lines 221 - 239, cmd_get(),
cmd_list(), and cmd_export_settings() still read settings by embedding the key
into jq source strings and using fallbacks like // empty or [[ -n ]], which
treats legitimate empty-string values as missing; update their read paths to
mirror cmd_set() by passing keys as data via --arg (or --argjson as needed) and
using getpath($k|split(".")) to fetch values, detecting "__MISSING__" when
absent and preserving empty strings and types. Concretely, replace any jq
expressions that interpolate keys or use hardcoded filter strings with patterns
like jq -r --arg k "$key" 'getpath($k|split(".")) // "__MISSING__"' (and
analogous list/export queries that map keys to getpath calls), remove the //
empty and [[ -n ]] presence checks, and ensure the subsequent logic treats
"__MISSING__" as absent while accepting "" as a valid value; use
_generate_defaults and the same getpath-based type extraction (e.g., | type) to
determine value types consistently with cmd_set().
♻️ Duplicate comments (1)
.agents/scripts/content-scanner-helper.sh (1)

305-313: ⚠️ Potential issue | 🟠 Major

The sentinel strip step still drops trailing newlines.

_cs_normalize_nfkc() preserves trailing \n by appending the sentinel before the first capture, but every caller then removes it with $(_cs_strip_normalize_sentinel ...). That second command substitution strips the trailing newlines again, so scan-file, scan-stdin, annotate-file, annotate-stdin, and the in-memory scan path still mutate payloads ending in newlines. Strip the sentinel from the already-captured variable instead of printing it back through $().

🛠️ Suggested direction
-_cs_strip_normalize_sentinel() {
+_cs_require_normalize_sentinel() {
   local content="$1"

   if [[ "$content" != *"$_CS_NORMALIZE_SENTINEL" ]]; then
     _cs_log_error "Normalization output missing sentinel"
     return 1
   fi

-  printf '%s' "${content%"$_CS_NORMALIZE_SENTINEL"}"
   return 0
 }
 normalized=$(_cs_normalize_nfkc "$content")
-if ! normalized=$(_cs_strip_normalize_sentinel "$normalized"); then
+if ! _cs_require_normalize_sentinel "$normalized"; then
   return 1
 fi
+normalized="${normalized%"$_CS_NORMALIZE_SENTINEL"}"

Apply the same in-place strip pattern at each file/stdin call site.

This demonstrates the shell behavior directly. Expected result: broken-bytes=5 and fixed-bytes=7, because the second $() drops the two trailing newlines.

#!/bin/bash
set -euo pipefail

sent="__CS_SENTINEL__"

normalize() {
  printf 'line1\n\n%s' "$sent"
}

strip_via_subshell() {
  local content="$1"
  printf '%s' "${content%"$sent"}"
}

captured=$(normalize)
broken=$(strip_via_subshell "$captured")
fixed="$captured"
fixed="${fixed%"$sent"}"

printf 'broken-bytes=%s\n' "$(printf '%s' "$broken" | wc -c | tr -d ' ')"
printf 'fixed-bytes=%s\n' "$(printf '%s' "$fixed" | wc -c | tr -d ' ')"

Based on learnings, trailing newlines in .agents/scripts/content-scanner-helper.sh _cs_normalize_nfkc() must be stripped with ${var%x} after the in-process sentinel, because a separate command substitution drops them again.

Also applies to: 375-376, 461-468, 480-485, 524-529, 543-548

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/content-scanner-helper.sh around lines 305 - 313, The
sentinel-strip approach currently uses a subshell printf via
_cs_strip_normalize_sentinel which causes trailing newlines to be lost; update
callers of _cs_normalize_nfkc() (e.g., scan-file, scan-stdin, annotate-file,
annotate-stdin and the in-memory scan path) to strip the sentinel in-place on
the captured variable instead of reprinting it: after capturing the normalized
output do captured_var="${captured_var%$_CS_NORMALIZE_SENTINEL}" (or use the
exact local var names used at each call site) rather than feeding the value
through $(_cs_strip_normalize_sentinel ...); you can keep
_cs_strip_normalize_sentinel for validation but change call sites to use the
shell parameter expansion pattern to preserve trailing newlines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In @.agents/scripts/settings-helper.sh:
- Around line 221-239: cmd_get(), cmd_list(), and cmd_export_settings() still
read settings by embedding the key into jq source strings and using fallbacks
like // empty or [[ -n ]], which treats legitimate empty-string values as
missing; update their read paths to mirror cmd_set() by passing keys as data via
--arg (or --argjson as needed) and using getpath($k|split(".")) to fetch values,
detecting "__MISSING__" when absent and preserving empty strings and types.
Concretely, replace any jq expressions that interpolate keys or use hardcoded
filter strings with patterns like jq -r --arg k "$key" 'getpath($k|split("."))
// "__MISSING__"' (and analogous list/export queries that map keys to getpath
calls), remove the // empty and [[ -n ]] presence checks, and ensure the
subsequent logic treats "__MISSING__" as absent while accepting "" as a valid
value; use _generate_defaults and the same getpath-based type extraction (e.g.,
| type) to determine value types consistently with cmd_set().

---

Duplicate comments:
In @.agents/scripts/content-scanner-helper.sh:
- Around line 305-313: The sentinel-strip approach currently uses a subshell
printf via _cs_strip_normalize_sentinel which causes trailing newlines to be
lost; update callers of _cs_normalize_nfkc() (e.g., scan-file, scan-stdin,
annotate-file, annotate-stdin and the in-memory scan path) to strip the sentinel
in-place on the captured variable instead of reprinting it: after capturing the
normalized output do captured_var="${captured_var%$_CS_NORMALIZE_SENTINEL}" (or
use the exact local var names used at each call site) rather than feeding the
value through $(_cs_strip_normalize_sentinel ...); you can keep
_cs_strip_normalize_sentinel for validation but change call sites to use the
shell parameter expansion pattern to preserve trailing newlines.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: caefdbd5-82f1-4e6c-9fed-f7a10c3f53f4

📥 Commits

Reviewing files that changed from the base of the PR and between 5bfb58d and 9045111.

📒 Files selected for processing (3)
  • .agents/scripts/content-scanner-helper.sh
  • .agents/scripts/deploy-agents-on-merge.sh
  • .agents/scripts/settings-helper.sh

@marcusquinn
Copy link
Owner Author

Dispatching worker to push PR #4126 to merge-ready state.\n- Model: alternating default via headless runtime helper\n- Branch: bugfix/recover-pr-3916-salvage\n- Scope: resolve actionable blockers and stabilize merge readiness\n- Attempt: 1 of 1\n- Direction: focus only on blockers preventing merge

@marcusquinn marcusquinn merged commit ff73758 into main Mar 11, 2026
21 checks passed
@marcusquinn marcusquinn deleted the bugfix/recover-pr-3916-salvage branch March 11, 2026 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant